diff --git a/.golangci.yml b/.golangci.yml
new file mode 100755
index 0000000..b84ded5
--- /dev/null
+++ b/.golangci.yml
@@ -0,0 +1,202 @@
+# NOTE: This file is populated by the lint-install tool. Local adjustments may be overwritten.
+linters-settings:
+  cyclop:
+    # NOTE: This is a very high transitional threshold
+    max-complexity: 37
+    package-average: 34.0
+    skip-tests: true
+
+  gocognit:
+    # NOTE: This is a very high transitional threshold
+    min-complexity: 98
+
+  dupl:
+    threshold: 200
+
+  goconst:
+    min-len: 4
+    min-occurrences: 5
+    ignore-tests: true
+
+  gosec:
+    excludes:
+      - G107 # Potential HTTP request made with variable url
+      - G204 # Subprocess launched with function call as argument or cmd arguments
+      - G404 # Use of weak random number generator (math/rand instead of crypto/rand
+
+  errorlint:
+    # these are still common in Go: for instance, exit errors.
+    asserts: false
+
+  exhaustive:
+    default-signifies-exhaustive: true
+
+  nestif:
+    min-complexity: 8
+
+  nolintlint:
+    require-explanation: true
+    allow-unused: false
+    require-specific: true
+
+  revive:
+    ignore-generated-header: true
+    severity: warning
+    rules:
+      - name: atomic
+      - name: blank-imports
+      - name: bool-literal-in-expr
+      - name: confusing-naming
+      - name: constant-logical-expr
+      - name: context-as-argument
+      - name: context-keys-type
+      - name: deep-exit
+      - name: defer
+      - name: range-val-in-closure
+      - name: range-val-address
+      - name: dot-imports
+      - name: error-naming
+      - name: error-return
+      - name: error-strings
+      - name: errorf
+      - name: exported
+      - name: identical-branches
+      - name: if-return
+      - name: import-shadowing
+      - name: increment-decrement
+      - name: indent-error-flow
+      - name: indent-error-flow
+      - name: package-comments
+      - name: range
+      - name: receiver-naming
+      - name: redefines-builtin-id
+      - name: superfluous-else
+      - name: struct-tag
+      - name: time-naming
+      - name: unexported-naming
+      - name: unexported-return
+      - name: unnecessary-stmt
+      - name: unreachable-code
+      - name: unused-parameter
+      - name: var-declaration
+      - name: var-naming
+      - name: unconditional-recursion
+      - name: waitgroup-by-value
+
+  staticcheck:
+    go: "1.16"
+
+  unused:
+    go: "1.16"
+
+output:
+  sort-results: true
+
+linters:
+  disable-all: true
+  enable:
+    - asciicheck
+    - bodyclose
+    - cyclop
+    - deadcode
+    - dogsled
+    - dupl
+    - durationcheck
+    - errcheck
+    # errname is only available in golangci-lint v1.42.0+ - wait until v1.43 is available to settle
+    #- errname
+    - errorlint
+    - exhaustive
+    - exportloopref
+    - forcetypeassert
+    - gocognit
+    - goconst
+    - gocritic
+    - godot
+    - gofmt
+    - gofumpt
+    - gosec
+    - goheader
+    - goimports
+    - goprintffuncname
+    - gosimple
+    - govet
+    - ifshort
+    - importas
+    - ineffassign
+    - makezero
+    - misspell
+    - nakedret
+    - nestif
+    - nilerr
+    - noctx
+    - nolintlint
+    - predeclared
+    # disabling for the initial iteration of the linting tool
+    #- promlinter
+    - revive
+    - rowserrcheck
+    - sqlclosecheck
+    - staticcheck
+    - structcheck
+    - stylecheck
+    - thelper
+    - tparallel
+    - typecheck
+    - unconvert
+    - unparam
+    - unused
+    - varcheck
+    - wastedassign
+    - whitespace
+
+  # Disabled linters, due to being misaligned with Go practices
+  # - exhaustivestruct
+  # - gochecknoglobals
+  # - gochecknoinits
+  # - goconst
+  # - godox
+  # - goerr113
+  # - gomnd
+  # - lll
+  # - nlreturn
+  # - testpackage
+  # - wsl
+  # Disabled linters, due to not being relevant to our code base:
+  # - maligned
+  # - prealloc "For most programs usage of prealloc will be a premature optimization."
+  # Disabled linters due to bad error messages or bugs
+  # - tagliatelle
+
+issues:
+  # Excluding configuration per-path, per-linter, per-text and per-source
+  exclude-rules:
+    - path: _test\.go
+      linters:
+        - gocyclo
+        - errcheck
+        - dupl
+        - gosec
+
+    - path: cmd.*
+      linters:
+        - noctx
+
+    - path: main\.go
+      linters:
+        - noctx
+
+    - path: cmd.*
+      text: "deep-exit"
+
+    - path: main\.go
+      text: "deep-exit"
+
+    # This check is of questionable value
+    - linters:
+        - tparallel
+      text: "call t.Parallel on the top level as well as its subtests"
+
+  # Don't hide lint issues just because there are many of them
+  max-same-issues: 0
+  max-issues-per-linter: 0
diff --git a/Makefile b/Makefile
new file mode 100755
index 0000000..924acf6
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,29 @@
+
+# BEGIN: lint-install ../infrastructure/
+# http://github.com/tinkerbell/lint-install
+
+GOLINT_VERSION ?= v1.42.0
+
+
+LINT_OS := $(shell uname)
+LINT_ARCH := $(shell uname -m)
+
+# shellcheck and hadolint lack arm64 native binaries: rely on x86-64 emulation
+ifeq ($(LINT_OS),Darwin)
+	ifeq ($(LINT_ARCH),arm64)
+		LINT_ARCH=x86_64
+	endif
+endif
+
+
+GOLINT_CONFIG:=$(shell dirname $(realpath $(firstword $(MAKEFILE_LIST))))/.golangci.yml
+
+lint: out/linters/golangci-lint-$(GOLINT_VERSION)-$(LINT_ARCH)
+	find . -name go.mod | xargs -n1 dirname | xargs -n1 -I{} sh -c "cd {} && golangci-lint run -c $(GOLINT_CONFIG)"
+
+out/linters/golangci-lint-$(GOLINT_VERSION)-$(LINT_ARCH):
+	mkdir -p out/linters
+	curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b out/linters $(GOLINT_VERSION)
+	mv out/linters/golangci-lint out/linters/golangci-lint-$(GOLINT_VERSION)-$(LINT_ARCH)
+
+# END: lint-install ../infrastructure/
diff --git a/pulumi/src/assets/main.go b/pulumi/src/assets/main.go
index 56802b4..122a09d 100644
--- a/pulumi/src/assets/main.go
+++ b/pulumi/src/assets/main.go
@@ -6,11 +6,11 @@ import (
 	"github.com/tinkerbell/infrastructure/src/internal"
 )
 
-type AssetsDns struct {
+type DNS struct {
 	Cname *ns1.Record
 }
 
-func CreateAssetsDns(ctx *pulumi.Context, infrastructure internal.Infrastructure) (AssetsDns, error) {
+func CreateDNS(ctx *pulumi.Context, infrastructure internal.Infrastructure) (DNS, error) {
 	// This verification record is required for Cloudflare to issue a certificate for this domain
 	_, err := ns1.NewRecord(ctx, "assets-cname-verification", &ns1.RecordArgs{
 		Zone:   pulumi.String(infrastructure.Zone.Zone),
@@ -22,9 +22,8 @@ func CreateAssetsDns(ctx *pulumi.Context, infrastructure internal.Infrastructure
 			},
 		},
 	})
-
 	if err != nil {
-		return AssetsDns{}, err
+		return DNS{}, err
 	}
 
 	// assets.tinkerbell.org
@@ -41,12 +40,11 @@ func CreateAssetsDns(ctx *pulumi.Context, infrastructure internal.Infrastructure
 			},
 		},
 	})
-
 	if err != nil {
-		return AssetsDns{}, err
+		return DNS{}, err
 	}
 
-	return AssetsDns{
+	return DNS{
 		Cname: cname,
 	}, nil
 }
diff --git a/pulumi/src/dns/tinkerbell.org/main.go b/pulumi/src/dns/tinkerbell.org/main.go
index 42fe45e..11f2dfa 100644
--- a/pulumi/src/dns/tinkerbell.org/main.go
+++ b/pulumi/src/dns/tinkerbell.org/main.go
@@ -5,11 +5,10 @@ import (
 	"github.com/pulumi/pulumi/sdk/v2/go/pulumi"
 )
 
-func ManageDns(ctx *pulumi.Context) (ns1.LookupZoneResult, error) {
+func ManageDNS(ctx *pulumi.Context) (ns1.LookupZoneResult, error) {
 	zone, err := ns1.LookupZone(ctx, &ns1.LookupZoneArgs{
 		Zone: "tinkerbell.org",
 	})
-
 	if err != nil {
 		return ns1.LookupZoneResult{}, err
 	}
diff --git a/pulumi/src/github-actions/cloud-init.go b/pulumi/src/github-actions/cloud-init.go
index 93a4132..fc2f227 100644
--- a/pulumi/src/github-actions/cloud-init.go
+++ b/pulumi/src/github-actions/cloud-init.go
@@ -7,13 +7,12 @@ import (
 )
 
 type MinionConfig struct {
-	MasterIp string
+	MasterIP string
 	States   []string
 }
 
 func cloudInitConfig(config *MinionConfig) string {
 	c, err := cloudinit.New("focal")
-
 	if err != nil {
 		panic(err)
 	}
@@ -32,27 +31,25 @@ schedule:
     minutes: 15
 
 grains:
-  role: github-action-runner`, 0644)
+  role: github-action-runner`, 0o644)
 
 	// I'm really trying to avoid bringing in any templating library to
 	// write this configuration, so I'm going to write each requested state to its
 	// own conf, which Salt will merge when the minion starts
 	// Sorry
 	for _, state := range config.States {
-		c.AddRunTextFile(fmt.Sprintf("/etc/salt/minion.d/gha_runner_state_%s.conf", state), fmt.Sprintf("grains:\n  gha_runner_states:\n    - %s", state), 0644)
+		c.AddRunTextFile(fmt.Sprintf("/etc/salt/minion.d/gha_runner_state_%s.conf", state), fmt.Sprintf("grains:\n  gha_runner_states:\n    - %s", state), 0o644)
 	}
 
-	c.AddRunCmd(fmt.Sprintf("echo master: %s > /etc/salt/minion.d/master.conf", config.MasterIp))
+	c.AddRunCmd(fmt.Sprintf("echo master: %s > /etc/salt/minion.d/master.conf", config.MasterIP))
 	c.AddRunCmd("systemctl daemon-reload")
 	c.AddRunCmd("systemctl enable salt-minion.service")
 	c.AddRunCmd("systemctl restart --no-block salt-minion.service")
 
 	script, err := c.RenderScript()
-
 	if err != nil {
 		panic(err)
 	}
 
 	return script
-
 }
diff --git a/pulumi/src/github-actions/main.go b/pulumi/src/github-actions/main.go
index 8cd819e..10616c7 100644
--- a/pulumi/src/github-actions/main.go
+++ b/pulumi/src/github-actions/main.go
@@ -14,19 +14,19 @@ type GitHubConfig struct {
 }
 
 // GitHubActionRunnerConfig is the struct we allow in the stack configuration
-// to describe the GitHubActionRunner we provision
+// to describe the GitHubActionRunner we provision.
 type GitHubActionRunnerConfig struct {
 	Facility equinix.Facility
 	Plan     equinix.Plan
 	States   []string
 }
 
-// GitHubActionRunner is the return struct for CreateSaltMaster
+// GitHubActionRunner is the return struct for CreateSaltMaster.
 type GitHubActionRunners struct {
 	Devices []equinix.Device
 }
 
-// CreateGitHubActionRunner Provisions a GitHub Action Runner
+// CreateGitHubActionRunner Provisions a GitHub Action Runner.
 func CreateGitHubActionRunners(ctx *pulumi.Context, infrastructure internal.Infrastructure) (GitHubActionRunners, error) {
 	metalConfig := config.New(ctx, "equinix-metal")
 	projectID := metalConfig.Require("projectId")
@@ -50,16 +50,15 @@ func CreateGitHubActionRunners(ctx *pulumi.Context, infrastructure internal.Infr
 				pulumi.String("role:github-action-runner"),
 			},
 			BillingCycle: equinix.BillingCycleHourly,
-			UserData: pulumi.All(infrastructure.SaltMasterIp, runner.States).ApplyT(func(args []interface{}) string {
+			UserData: pulumi.All(infrastructure.SaltMasterIP, runner.States).ApplyT(func(args []interface{}) string {
 				return cloudInitConfig(&MinionConfig{
-					MasterIp: args[0].(string),
+					MasterIP: args[0].(string),
 					States:   args[1].([]string),
 				})
 			}).(pulumi.StringOutput),
 		}
 
 		device, err := equinix.NewDevice(ctx, fmt.Sprintf("github-action-runner-%d", i), &deviceArgs)
-
 		if err != nil {
 			return GitHubActionRunners{}, err
 		}
diff --git a/pulumi/src/internal/main.go b/pulumi/src/internal/main.go
index 047b3a7..2ac4ed0 100644
--- a/pulumi/src/internal/main.go
+++ b/pulumi/src/internal/main.go
@@ -10,5 +10,5 @@ import (
 // with DNS zone being the first use-case.
 type Infrastructure struct {
 	Zone         ns1.LookupZoneResult
-	SaltMasterIp pulumi.StringOutput
+	SaltMasterIP pulumi.StringOutput
 }
diff --git a/pulumi/src/main.go b/pulumi/src/main.go
index 9b58c00..6261ca2 100644
--- a/pulumi/src/main.go
+++ b/pulumi/src/main.go
@@ -11,7 +11,7 @@ import (
 
 func main() {
 	pulumi.Run(func(ctx *pulumi.Context) error {
-		zone, err := tinkdns.ManageDns(ctx)
+		zone, err := tinkdns.ManageDNS(ctx)
 		if err != nil {
 			return err
 		}
@@ -21,12 +21,11 @@ func main() {
 		}
 
 		saltMaster, err := master.CreateSaltMaster(ctx, infrastructure)
-
 		if err != nil {
 			return err
 		}
 
-		infrastructure.SaltMasterIp = saltMaster.Device.AccessPrivateIpv4
+		infrastructure.SaltMasterIP = saltMaster.Device.AccessPrivateIpv4
 
 		_, err = runners.CreateGitHubActionRunners(ctx, infrastructure)
 
@@ -34,7 +33,7 @@ func main() {
 			return err
 		}
 
-		_, err = assets.CreateAssetsDns(ctx, infrastructure)
+		_, err = assets.CreateDNS(ctx, infrastructure)
 		if err != nil {
 			return err
 		}
diff --git a/pulumi/src/saltstack/master/cloud-init.go b/pulumi/src/saltstack/master/cloud-init.go
index bb686e2..8720928 100644
--- a/pulumi/src/saltstack/master/cloud-init.go
+++ b/pulumi/src/saltstack/master/cloud-init.go
@@ -21,7 +21,6 @@ type BootstrapConfig struct {
 
 func cloudInitConfig(config *BootstrapConfig) string {
 	c, err := cloudinit.New("focal")
-
 	if err != nil {
 		panic(err)
 	}
@@ -54,7 +53,7 @@ ext_pillar:
   - git:
       - main https://github.com/tinkerbell/infrastructure:
           - root: saltstack/pillar
-          - env: main`, 0644)
+          - env: main`, 0o644)
 
 	c.AddRunTextFile("/etc/salt/minion.d/minion.conf", `autosign_grains:
 - role
@@ -66,7 +65,7 @@ schedule:
     minutes: 15
 
 grains:
-  role: master`, 0644)
+  role: master`, 0o644)
 
 	c.AddRunCmd("PRIVATE_IP=$(curl -s https://metadata.platformequinix.com/metadata | jq -r '.network.addresses | map(select(.public==false)) | first | .address')")
 
@@ -84,16 +83,16 @@ grains:
 
   'G@role:github-action-runner':
     - github
-`, 0644)
+`, 0o644)
 
 	c.AddRunCmd("mkdir -p /srv/pillar/teleport")
-	c.AddRunTextFile("/srv/pillar/teleport/init.sls", fmt.Sprintf("teleport:\n  domain: %s\n  clientId: %s\n  clientSecret: %s\n", config.teleportDomain, config.teleportClientID, config.teleportClientSecret), 0400)
-	c.AddRunTextFile("/srv/pillar/teleport/node.sls", fmt.Sprintf("teleport:\n  peerToken: %s\n", config.teleportPeerToken), 0400)
-	c.AddRunTextFile("/srv/pillar/github.sls", fmt.Sprintf("github:\n  username: %s\n  accessToken: %s\n", config.githubUsername, config.githubAccessToken), 0400)
-	c.AddRunTextFile("/srv/pillar/aws.sls", fmt.Sprintf("s3:\n  keyid: %s\n  key: %s\n  location: %s\n  bucketName: %s\n", config.awsAccessKeyID, config.awsSecretAccessKey, config.awsBucketLocation, config.awsBucketName), 0400)
+	c.AddRunTextFile("/srv/pillar/teleport/init.sls", fmt.Sprintf("teleport:\n  domain: %s\n  clientId: %s\n  clientSecret: %s\n", config.teleportDomain, config.teleportClientID, config.teleportClientSecret), 0o400)
+	c.AddRunTextFile("/srv/pillar/teleport/node.sls", fmt.Sprintf("teleport:\n  peerToken: %s\n", config.teleportPeerToken), 0o400)
+	c.AddRunTextFile("/srv/pillar/github.sls", fmt.Sprintf("github:\n  username: %s\n  accessToken: %s\n", config.githubUsername, config.githubAccessToken), 0o400)
+	c.AddRunTextFile("/srv/pillar/aws.sls", fmt.Sprintf("s3:\n  keyid: %s\n  key: %s\n  location: %s\n  bucketName: %s\n", config.awsAccessKeyID, config.awsSecretAccessKey, config.awsBucketLocation, config.awsBucketName), 0o400)
 
 	// https://github.com/saltstack/salt/issues/60222
-	c.AddRunTextFile("/srv/pillar/aws-eurgh.sls", fmt.Sprintf("s3.keyid: %s\ns3.key: %s\ns3.location: %s\ns3.bucketName: %s\n", config.awsAccessKeyID, config.awsSecretAccessKey, config.awsBucketLocation, config.awsBucketName), 0400)
+	c.AddRunTextFile("/srv/pillar/aws-eurgh.sls", fmt.Sprintf("s3.keyid: %s\ns3.key: %s\ns3.location: %s\ns3.bucketName: %s\n", config.awsAccessKeyID, config.awsSecretAccessKey, config.awsBucketLocation, config.awsBucketName), 0o400)
 
 	c.AddRunCmd("echo interface: ${PRIVATE_IP} > /etc/salt/master.d/private-interface.conf")
 	c.AddRunCmd("echo master: ${PRIVATE_IP} > /etc/salt/minion.d/master.conf")
@@ -106,11 +105,9 @@ grains:
 	c.AddRunCmd("systemctl restart --no-block salt-minion.service")
 
 	script, err := c.RenderScript()
-
 	if err != nil {
 		panic(err)
 	}
 
 	return script
-
 }
diff --git a/pulumi/src/saltstack/master/main.go b/pulumi/src/saltstack/master/main.go
index 1ea39f9..f0c827e 100644
--- a/pulumi/src/saltstack/master/main.go
+++ b/pulumi/src/saltstack/master/main.go
@@ -12,13 +12,13 @@ import (
 )
 
 // SaltMasterConfig is the struct we allow in the stack configuration
-// to describe the SaltMaster we provision
+// to describe the SaltMaster we provision.
 type SaltMasterConfig struct {
 	Facility equinix.Facility
 	Plan     equinix.Plan
 }
 
-// SaltMaster is the return struct for CreateSaltMaster
+// SaltMaster is the return struct for CreateSaltMaster.
 type SaltMaster struct {
 	Device equinix.Device
 }
@@ -41,7 +41,7 @@ type AwsConfig struct {
 	BucketLocation  string
 }
 
-// CreateSaltMaster Provisions a SaltMaster
+// CreateSaltMaster Provisions a SaltMaster.
 func CreateSaltMaster(ctx *pulumi.Context, infrastructure internal.Infrastructure) (SaltMaster, error) {
 	metalConfig := config.New(ctx, "equinix-metal")
 	projectID := metalConfig.Require("projectId")
@@ -55,6 +55,9 @@ func CreateSaltMaster(ctx *pulumi.Context, infrastructure internal.Infrastructur
 		ProjectId: pulumi.String(projectID),
 		Quantity:  pulumi.Int(1),
 	})
+	if err != nil {
+		return SaltMaster{}, err
+	}
 
 	var teleportConfig TeleportConfig
 	stackConfig.RequireObject("teleport", &teleportConfig)
@@ -62,7 +65,6 @@ func CreateSaltMaster(ctx *pulumi.Context, infrastructure internal.Infrastructur
 
 	// Generate random PeerToken for Teleport cluster
 	peerToken, err := random.NewRandomUuid(ctx, "teleport-peer-token", nil)
-
 	if err != nil {
 		return SaltMaster{}, err
 	}
diff --git a/pulumi/src/utils.go b/pulumi/src/utils.go
deleted file mode 100644
index eb0c34f..0000000
--- a/pulumi/src/utils.go
+++ /dev/null
@@ -1,12 +0,0 @@
-package main
-
-import (
-	"fmt"
-
-	"github.com/pulumi/pulumi/sdk/v2/go/pulumi"
-)
-
-func StackName(ctx *pulumi.Context, name string) string {
-
-	return fmt.Sprintf("%s-%s", ctx.Stack(), name)
-}