Repository: GoogleContainerTools/skaffold
Pull request #7666 (2022-07-21 02:05:59)

feat(ko): Init support for the ko builder
Adds the ko builder as an option when running `skaffold init` in a directory containing Go code.

The ko builder will show up as an option for directories containing a `go.mod` file.

The feature can be disabled by adding the `--XXenableKoInit=false` flag.

Tracking: #7131


codecov[bot]: # [Codecov](https://codecov.io/gh/GoogleContainerTools/skaffold/pull/7666?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools) Report
> Merging [#7666](https://codecov.io/gh/GoogleContainerTools/skaffold/pull/7666?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools) (f19c801) into [main](https://codecov.io/gh/GoogleContainerTools/skaffold/commit/290280e8c6c19231e9ed5b5c027f2c01d22f75be?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools) (290280e) will **decrease** coverage by `3.97%`.
> The diff coverage is `54.44%`.

```diff
@@            Coverage Diff             @@
##             main    #7666      +/-   ##
==========================================
- Coverage   70.48%   66.51%   -3.98%     
==========================================
  Files         515      586      +71     
  Lines       23150    28107    +4957     
==========================================
+ Hits        16317    18694    +2377     
- Misses       5776     8043    +2267     
- Partials     1057     1370     +313     
```


| [Impacted Files](https://codecov.io/gh/GoogleContainerTools/skaffold/pull/7666?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools) | Coverage Δ | |
|---|---|---|
| [cmd/skaffold/app/cmd/credits/export.go](https://codecov.io/gh/GoogleContainerTools/skaffold/pull/7666/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools#diff-Y21kL3NrYWZmb2xkL2FwcC9jbWQvY3JlZGl0cy9leHBvcnQuZ28=) | `0.00% <0.00%> (ø)` | |
| [cmd/skaffold/app/cmd/deploy.go](https://codecov.io/gh/GoogleContainerTools/skaffold/pull/7666/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools#diff-Y21kL3NrYWZmb2xkL2FwcC9jbWQvZGVwbG95Lmdv) | `40.90% <0.00%> (-12.94%)` | :arrow_down: |
| [cmd/skaffold/app/cmd/test.go](https://codecov.io/gh/GoogleContainerTools/skaffold/pull/7666/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools#diff-Y21kL3NrYWZmb2xkL2FwcC9jbWQvdGVzdC5nbw==) | `44.44% <0.00%> (ø)` | |
| [cmd/skaffold/skaffold.go](https://codecov.io/gh/GoogleContainerTools/skaffold/pull/7666/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools#diff-Y21kL3NrYWZmb2xkL3NrYWZmb2xkLmdv) | `0.00% <0.00%> (ø)` | |
| [cmd/skaffold/app/cmd/inspect\_tests.go](https://codecov.io/gh/GoogleContainerTools/skaffold/pull/7666/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools#diff-Y21kL3NrYWZmb2xkL2FwcC9jbWQvaW5zcGVjdF90ZXN0cy5nbw==) | `62.50% <14.28%> (-1.14%)` | :arrow_down: |
| [cmd/skaffold/app/cmd/render.go](https://codecov.io/gh/GoogleContainerTools/skaffold/pull/7666/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools#diff-Y21kL3NrYWZmb2xkL2FwcC9jbWQvcmVuZGVyLmdv) | `35.48% <18.18%> (-5.90%)` | :arrow_down: |
| [cmd/skaffold/app/cmd/lsp.go](https://codecov.io/gh/GoogleContainerTools/skaffold/pull/7666/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools#diff-Y21kL3NrYWZmb2xkL2FwcC9jbWQvbHNwLmdv) | `28.12% <28.12%> (ø)` | |
| [cmd/skaffold/app/cmd/run.go](https://codecov.io/gh/GoogleContainerTools/skaffold/pull/7666/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools#diff-Y21kL3NrYWZmb2xkL2FwcC9jbWQvcnVuLmdv) | `64.28% <33.33%> (-9.63%)` | :arrow_down: |
| [cmd/skaffold/app/cmd/fix.go](https://codecov.io/gh/GoogleContainerTools/skaffold/pull/7666/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools#diff-Y21kL3NrYWZmb2xkL2FwcC9jbWQvZml4Lmdv) | `55.93% <38.88%> (-20.54%)` | :arrow_down: |
| [cmd/skaffold/app/cmd/verify.go](https://codecov.io/gh/GoogleContainerTools/skaffold/pull/7666/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools#diff-Y21kL3NrYWZmb2xkL2FwcC9jbWQvdmVyaWZ5Lmdv) | `41.17% <41.17%> (ø)` | |
| ... and [332 more](https://codecov.io/gh/GoogleContainerTools/skaffold/pull/7666/diff?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools) | |

:mega: Codecov can now indicate which changes are the most critical in Pull Requests. [Learn more](https://about.codecov.io/product/feature/runtime-insights/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools)


Patch:
diff --git a/cmd/skaffold/app/cmd/delete.go b/cmd/skaffold/app/cmd/delete.go
index 3a0285529..a2e350ab9 100644
--- a/cmd/skaffold/app/cmd/delete.go
+++ b/cmd/skaffold/app/cmd/delete.go
@@ -18,12 +18,11 @@ package cmd
 
 import (
 	"context"
-	"fmt"
 	"io"
 
 	"github.com/spf13/cobra"
 
-	deployutil "github.com/GoogleContainerTools/skaffold/pkg/skaffold/deploy/util"
+	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/graph"
 	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/runner"
 	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/util"
 )
@@ -46,10 +45,10 @@ func NewCmdDelete() *cobra.Command {
 
 func doDelete(ctx context.Context, out io.Writer) error {
 	return withRunner(ctx, out, func(r runner.Runner, configs []util.VersionedConfig) error {
-		manifests, err := deployutil.GetManifestsFromHydrationDir(ctx, opts)
+		manifestListByConfig, err := r.Render(ctx, io.Discard, []graph.Artifact{}, false)
 		if err != nil {
-			return fmt.Errorf("getting manifests from hydration dir: %w", err)
+			return err
 		}
-		return r.Cleanup(ctx, out, dryRun, manifests)
+		return r.Cleanup(ctx, out, dryRun, manifestListByConfig)
 	})
 }
diff --git a/cmd/skaffold/app/cmd/dev.go b/cmd/skaffold/app/cmd/dev.go
index 5b41a986a..8c00af02a 100644
--- a/cmd/skaffold/app/cmd/dev.go
+++ b/cmd/skaffold/app/cmd/dev.go
@@ -24,7 +24,7 @@ import (
 
 	"github.com/spf13/cobra"
 
-	deployutil "github.com/GoogleContainerTools/skaffold/pkg/skaffold/deploy/util"
+	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/graph"
 	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/output/log"
 	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/runner"
 	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest"
@@ -74,11 +74,11 @@ func runDev(ctx context.Context, out io.Writer) error {
 
 				if r.HasDeployed() {
 					cleanup = func() {
-						manifests, err := deployutil.GetManifestsFromHydrationDir(ctx, opts)
+						manifestsByConfig, err := r.Render(ctx, io.Discard, []graph.Artifact{}, false)
 						if err != nil {
-							log.Entry(ctx).Warn(fmt.Errorf("getting manifests from hydration dir: %w", err))
+							log.Entry(ctx).Warn(fmt.Errorf("failed to render manifests: %w", err))
 						}
-						if err := r.Cleanup(context.Background(), out, false, manifests); err != nil {
+						if err := r.Cleanup(context.Background(), out, false, manifestsByConfig); err != nil {
 							log.Entry(ctx).Warn("deployer cleanup:", err)
 						}
 					}
diff --git a/cmd/skaffold/app/cmd/init.go b/cmd/skaffold/app/cmd/init.go
index f0a0660a4..684d1e57c 100644
--- a/cmd/skaffold/app/cmd/init.go
+++ b/cmd/skaffold/app/cmd/init.go
@@ -40,6 +40,7 @@ var (
 	analyze                  bool
 	enableJibInit            bool
 	enableJibGradleInit      bool
+	enableKoInit             bool
 	enableBuildpacksInit     bool
 	enableNewInitFormat      bool
 	enableManifestGeneration bool
@@ -65,6 +66,7 @@ func NewCmdInit() *cobra.Command {
 			{Value: &enableNewInitFormat, Name: "XXenableNewInitFormat", DefValue: false, Usage: "", Hidden: true, IsEnum: true},
 			{Value: &enableJibInit, Name: "XXenableJibInit", DefValue: true, Usage: "", Hidden: true, IsEnum: true},
 			{Value: &enableJibGradleInit, Name: "XXenableJibGradleInit", DefValue: false, Usage: "", Hidden: true, IsEnum: true},
+			{Value: &enableKoInit, Name: "XXenableKoInit", DefValue: false, Usage: "", Hidden: true, IsEnum: true},
 			{Value: &enableBuildpacksInit, Name: "XXenableBuildpacksInit", DefValue: true, Usage: "", Hidden: true, IsEnum: true},
 			{Value: &buildpacksBuilder, Name: "XXdefaultBuildpacksBuilder", DefValue: "gcr.io/buildpacks/builder:v1", Usage: "", Hidden: true},
 			{Value: &enableManifestGeneration, Name: "generate-manifests", DefValue: false, Usage: "Allows skaffold to try and generate basic kubernetes resources to get your project started", IsEnum: true},
@@ -85,8 +87,9 @@ func doInit(ctx context.Context, out io.Writer) error {
 		Analyze:                  analyze,
 		EnableJibInit:            enableJibInit,
 		EnableJibGradleInit:      enableJibGradleInit,
+		EnableKoInit:             enableKoInit,
 		EnableBuildpacksInit:     enableBuildpacksInit,
-		EnableNewInitFormat:      enableNewInitFormat || enableBuildpacksInit || enableJibInit,
+		EnableNewInitFormat:      enableNewInitFormat || enableBuildpacksInit || enableJibInit || enableKoInit,
 		EnableManifestGeneration: enableManifestGeneration,
 		Opts:                     opts,
 		MaxFileSize:              maxFileSize,
diff --git a/docs-v2/content/en/docs/pipeline-stages/builders/ko.md b/docs-v2/content/en/docs/pipeline-stages/builders/ko.md
index af0994be8..86c5ac7c4 100644
--- a/docs-v2/content/en/docs/pipeline-stages/builders/ko.md
+++ b/docs-v2/content/en/docs/pipeline-stages/builders/ko.md
@@ -254,30 +254,10 @@ Useful tips for existing `ko` users:
   specify these fields in `.ko.yaml`, you do not need to repeat them in
   `skaffold.yaml`.
 
-- Future Skaffold releases will include support for generating `skaffold.yaml`
-  files by examining an existing code base. For now, you can generate a starter
-  `skaffold.yaml` file by searching your existing manifests for image
-  references starting with `ko://` by using this snippet:
-
-  ```shell
-  cat << EOF > skaffold.yaml
-  apiVersion: skaffold/v2beta26
-  kind: Config
-  build:
-    artifacts:
-  EOF
-  grep -rho "ko://$(go list -m)[^\"]*" ./config/ | sort | uniq | xargs -Iimg echo -e "  - image: img\n    ko: {}" >> skaffold.yaml
-  cat << EOF >> skaffold.yaml
-    local:
-      concurrency: 0
-  deploy:
-    kubectl:
-      manifests:
-      - ./config/**
-  EOF
-  ```
-
-  Replace `./config/` with the path to your Kubernetes manifest files.
+- You can generate `skaffold.yaml` files by examining an existing code base,
+  using the command
+  [`skaffold init --XXenableKoInit=true`]({{< relref "/docs/pipeline-stages/init" >}}).
+  Select the Ko builder for your images when prompted.
 
 ### `ko` commands and workflows in Skaffold
 
diff --git a/docs-v2/content/en/docs/pipeline-stages/init.md b/docs-v2/content/en/docs/pipeline-stages/init.md
index a719db9c8..0fdf0c2e9 100644
--- a/docs-v2/content/en/docs/pipeline-stages/init.md
+++ b/docs-v2/content/en/docs/pipeline-stages/init.md
@@ -17,7 +17,8 @@ and [deploy](#deploy-config-initialization) config.
 
 1. [Docker]({{<relref "/docs/pipeline-stages/builders/docker">}})
 2. [Jib]({{<relref "/docs/pipeline-stages/builders/jib">}})
-2. [Buildpacks]({{<relref "/docs/pipeline-stages/builders/buildpacks">}})
+3. [Ko]({{<relref "/docs/pipeline-stages/builders/ko">}})
+4. [Buildpacks]({{<relref "/docs/pipeline-stages/builders/buildpacks">}})
 
 `skaffold init` walks your project directory and looks for any build configuration files such as `Dockerfile`,
 `build.gradle/pom.xml`, `package.json`, `requirements.txt` or `go.mod`. `init` skips files that are larger
@@ -108,7 +109,7 @@ If bringing a project to skaffold that has no kubernetes manifests yet, it may b
 ## `--force` Flag
 `skaffold init` allows for use of a `--force` flag, which removes the prompts from vanilla `skaffold init`, and allows skaffold to make a best effort attempt to automatically generate a config for your project.
 
-In a situation where one image is detected, but multiple possible builders are detected, skaffold will choose a builder as follows: Docker > Jib > Bazel > Buildpacks.
+In a situation where one image is detected, but multiple possible builders are detected, skaffold will choose a builder as follows: Docker > Jib > Ko > Bazel > Buildpacks.
 
 *Note: This feature is still under development, and doesn't currently support use cases such as multiple images in a project.*
 
diff --git a/docs/content/en/docs/pipeline-stages/builders/ko.md b/docs/content/en/docs/pipeline-stages/builders/ko.md
index af0994be8..86c5ac7c4 100644
--- a/docs/content/en/docs/pipeline-stages/builders/ko.md
+++ b/docs/content/en/docs/pipeline-stages/builders/ko.md
@@ -254,30 +254,10 @@ Useful tips for existing `ko` users:
   specify these fields in `.ko.yaml`, you do not need to repeat them in
   `skaffold.yaml`.
 
-- Future Skaffold releases will include support for generating `skaffold.yaml`
-  files by examining an existing code base. For now, you can generate a starter
-  `skaffold.yaml` file by searching your existing manifests for image
-  references starting with `ko://` by using this snippet:
-
-  ```shell
-  cat << EOF > skaffold.yaml
-  apiVersion: skaffold/v2beta26
-  kind: Config
-  build:
-    artifacts:
-  EOF
-  grep -rho "ko://$(go list -m)[^\"]*" ./config/ | sort | uniq | xargs -Iimg echo -e "  - image: img\n    ko: {}" >> skaffold.yaml
-  cat << EOF >> skaffold.yaml
-    local:
-      concurrency: 0
-  deploy:
-    kubectl:
-      manifests:
-      - ./config/**
-  EOF
-  ```
-
-  Replace `./config/` with the path to your Kubernetes manifest files.
+- You can generate `skaffold.yaml` files by examining an existing code base,
+  using the command
+  [`skaffold init --XXenableKoInit=true`]({{< relref "/docs/pipeline-stages/init" >}}).
+  Select the Ko builder for your images when prompted.
 
 ### `ko` commands and workflows in Skaffold
 
diff --git a/docs/content/en/docs/pipeline-stages/init.md b/docs/content/en/docs/pipeline-stages/init.md
index a719db9c8..0fdf0c2e9 100644
--- a/docs/content/en/docs/pipeline-stages/init.md
+++ b/docs/content/en/docs/pipeline-stages/init.md
@@ -17,7 +17,8 @@ and [deploy](#deploy-config-initialization) config.
 
 1. [Docker]({{<relref "/docs/pipeline-stages/builders/docker">}})
 2. [Jib]({{<relref "/docs/pipeline-stages/builders/jib">}})
-2. [Buildpacks]({{<relref "/docs/pipeline-stages/builders/buildpacks">}})
+3. [Ko]({{<relref "/docs/pipeline-stages/builders/ko">}})
+4. [Buildpacks]({{<relref "/docs/pipeline-stages/builders/buildpacks">}})
 
 `skaffold init` walks your project directory and looks for any build configuration files such as `Dockerfile`,
 `build.gradle/pom.xml`, `package.json`, `requirements.txt` or `go.mod`. `init` skips files that are larger
@@ -108,7 +109,7 @@ If bringing a project to skaffold that has no kubernetes manifests yet, it may b
 ## `--force` Flag
 `skaffold init` allows for use of a `--force` flag, which removes the prompts from vanilla `skaffold init`, and allows skaffold to make a best effort attempt to automatically generate a config for your project.
 
-In a situation where one image is detected, but multiple possible builders are detected, skaffold will choose a builder as follows: Docker > Jib > Bazel > Buildpacks.
+In a situation where one image is detected, but multiple possible builders are detected, skaffold will choose a builder as follows: Docker > Jib > Ko > Bazel > Buildpacks.
 
 *Note: This feature is still under development, and doesn't currently support use cases such as multiple images in a project.*
 
diff --git a/examples/compose/Dockerfile b/examples/compose/Dockerfile
index 415a8456f..67f8c3cbb 100644
--- a/examples/compose/Dockerfile
+++ b/examples/compose/Dockerfile
@@ -1,8 +1,10 @@
-FROM golang:1.15 as builder
+FROM golang:1.18 as builder
+WORKDIR /code
 COPY main.go .
+COPY go.mod .
 # `skaffold debug` sets SKAFFOLD_GO_GCFLAGS to disable compiler optimizations
 ARG SKAFFOLD_GO_GCFLAGS
-RUN go build -gcflags="${SKAFFOLD_GO_GCFLAGS}" -o /app main.go
+RUN go build -gcflags="${SKAFFOLD_GO_GCFLAGS}" -trimpath -o /app main.go
 
 FROM alpine:3
 # Define GOTRACEBACK to mark this container as using the Go language runtime
diff --git a/examples/compose/go.mod b/examples/compose/go.mod
new file mode 100644
index 000000000..9023b37a8
--- /dev/null
+++ b/examples/compose/go.mod
@@ -0,0 +1,3 @@
+module github.com/GoogleContainerTools/skaffold/examples/compose
+
+go 1.18
diff --git a/examples/cross-platform-builds/Dockerfile b/examples/cross-platform-builds/Dockerfile
index 0066fb077..326833f37 100644
--- a/examples/cross-platform-builds/Dockerfile
+++ b/examples/cross-platform-builds/Dockerfile
@@ -1,10 +1,12 @@
-FROM --platform=$BUILDPLATFORM golang:1.15 as builder
+FROM --platform=$BUILDPLATFORM golang:1.18 as builder
+WORKDIR /code
 COPY main.go .
+COPY go.mod .
 # `skaffold debug` sets SKAFFOLD_GO_GCFLAGS to disable compiler optimizations
 ARG TARGETOS
 ARG TARGETARCH
 ARG SKAFFOLD_GO_GCFLAGS
-RUN GOOS=$TARGETOS GOARCH=$TARGETARCH go build -gcflags="${SKAFFOLD_GO_GCFLAGS}" -o /app main.go
+RUN GOOS=$TARGETOS GOARCH=$TARGETARCH go build -gcflags="${SKAFFOLD_GO_GCFLAGS}" -trimpath -o /app main.go
 
 FROM alpine:3
 # Define GOTRACEBACK to mark this container as using the Go language runtime
diff --git a/examples/cross-platform-builds/go.mod b/examples/cross-platform-builds/go.mod
new file mode 100644
index 000000000..c4d9607a3
--- /dev/null
+++ b/examples/cross-platform-builds/go.mod
@@ -0,0 +1,3 @@
+module github.com/GoogleContainerTools/skaffold/examples/cross-platform-builds
+
+go 1.18
diff --git a/examples/custom-buildx/Dockerfile b/examples/custom-buildx/Dockerfile
index d33b6af7c..dbe5a0f2a 100644
--- a/examples/custom-buildx/Dockerfile
+++ b/examples/custom-buildx/Dockerfile
@@ -5,13 +5,15 @@
 # TARGET{PLATFORM,OS,ARCH} are set to the desired platform
 FROM --platform=$BUILDPLATFORM golang:alpine AS builder
 
+WORKDIR /code
 COPY main.go .
+COPY go.mod .
 
 # `skaffold debug` sets SKAFFOLD_GO_GCFLAGS to disable compiler optimizations
 ARG SKAFFOLD_GO_GCFLAGS
 ARG TARGETOS
 ARG TARGETARCH
-RUN GOOS=$TARGETOS GOARCH=$TARGETARCH go build -gcflags="${SKAFFOLD_GO_GCFLAGS}" -o /app main.go
+RUN GOOS=$TARGETOS GOARCH=$TARGETARCH go build -gcflags="${SKAFFOLD_GO_GCFLAGS}" -trimpath -o /app main.go
 
 FROM alpine:3
 # Define GOTRACEBACK to mark this container as using the Go language runtime
diff --git a/examples/docker-deploy/bert/Dockerfile b/examples/docker-deploy/bert/Dockerfile
index 19becf992..c01cebfca 100644
--- a/examples/docker-deploy/bert/Dockerfile
+++ b/examples/docker-deploy/bert/Dockerfile
@@ -1,8 +1,10 @@
-FROM golang:1.15 as builder
+FROM golang:1.18 as builder
+WORKDIR /code
 COPY main.go .
+COPY go.mod .
 # `skaffold debug` sets SKAFFOLD_GO_GCFLAGS to disable compiler optimizations
 ARG SKAFFOLD_GO_GCFLAGS
-RUN go build -gcflags="${SKAFFOLD_GO_GCFLAGS}" -o /app main.go
+RUN go build -gcflags="${SKAFFOLD_GO_GCFLAGS}" -trimpath -o /app main.go
 
 FROM alpine:3.10
 # Define GOTRACEBACK to mark this container as using the Go language runtime
diff --git a/examples/docker-deploy/bert/go.mod b/examples/docker-deploy/bert/go.mod
new file mode 100644
index 000000000..61fcc27b5
--- /dev/null
+++ b/examples/docker-deploy/bert/go.mod
@@ -0,0 +1,3 @@
+module github.com/GoogleContainerTools/skaffold/examples/docker-deploy/bert
+
+go 1.18
diff --git a/examples/docker-deploy/ernie/Dockerfile b/examples/docker-deploy/ernie/Dockerfile
index 19becf992..c01cebfca 100644
--- a/examples/docker-deploy/ernie/Dockerfile
+++ b/examples/docker-deploy/ernie/Dockerfile
@@ -1,8 +1,10 @@
-FROM golang:1.15 as builder
+FROM golang:1.18 as builder
+WORKDIR /code
 COPY main.go .
+COPY go.mod .
 # `skaffold debug` sets SKAFFOLD_GO_GCFLAGS to disable compiler optimizations
 ARG SKAFFOLD_GO_GCFLAGS
-RUN go build -gcflags="${SKAFFOLD_GO_GCFLAGS}" -o /app main.go
+RUN go build -gcflags="${SKAFFOLD_GO_GCFLAGS}" -trimpath -o /app main.go
 
 FROM alpine:3.10
 # Define GOTRACEBACK to mark this container as using the Go language runtime
diff --git a/examples/docker-deploy/ernie/go.mod b/examples/docker-deploy/ernie/go.mod
new file mode 100644
index 000000000..2fb116270
--- /dev/null
+++ b/examples/docker-deploy/ernie/go.mod
@@ -0,0 +1,3 @@
+module github.com/GoogleContainerTools/skaffold/examples/docker-deploy/ernie
+
+go 1.18
diff --git a/examples/gcb-kaniko/Dockerfile b/examples/gcb-kaniko/Dockerfile
index 415a8456f..67f8c3cbb 100644
--- a/examples/gcb-kaniko/Dockerfile
+++ b/examples/gcb-kaniko/Dockerfile
@@ -1,8 +1,10 @@
-FROM golang:1.15 as builder
+FROM golang:1.18 as builder
+WORKDIR /code
 COPY main.go .
+COPY go.mod .
 # `skaffold debug` sets SKAFFOLD_GO_GCFLAGS to disable compiler optimizations
 ARG SKAFFOLD_GO_GCFLAGS
-RUN go build -gcflags="${SKAFFOLD_GO_GCFLAGS}" -o /app main.go
+RUN go build -gcflags="${SKAFFOLD_GO_GCFLAGS}" -trimpath -o /app main.go
 
 FROM alpine:3
 # Define GOTRACEBACK to mark this container as using the Go language runtime
diff --git a/examples/gcb-kaniko/go.mod b/examples/gcb-kaniko/go.mod
new file mode 100644
index 000000000..66cb3926a
--- /dev/null
+++ b/examples/gcb-kaniko/go.mod
@@ -0,0 +1,3 @@
+module github.com/GoogleContainerTools/skaffold/examples/gcb-kaniko
+
+go 1.18
diff --git a/examples/generate-pipeline/Dockerfile b/examples/generate-pipeline/Dockerfile
index 415a8456f..67f8c3cbb 100644
--- a/examples/generate-pipeline/Dockerfile
+++ b/examples/generate-pipeline/Dockerfile
@@ -1,8 +1,10 @@
-FROM golang:1.15 as builder
+FROM golang:1.18 as builder
+WORKDIR /code
 COPY main.go .
+COPY go.mod .
 # `skaffold debug` sets SKAFFOLD_GO_GCFLAGS to disable compiler optimizations
 ARG SKAFFOLD_GO_GCFLAGS
-RUN go build -gcflags="${SKAFFOLD_GO_GCFLAGS}" -o /app main.go
+RUN go build -gcflags="${SKAFFOLD_GO_GCFLAGS}" -trimpath -o /app main.go
 
 FROM alpine:3
 # Define GOTRACEBACK to mark this container as using the Go language runtime
diff --git a/examples/generate-pipeline/go.mod b/examples/generate-pipeline/go.mod
new file mode 100644
index 000000000..d78147d43
--- /dev/null
+++ b/examples/generate-pipeline/go.mod
@@ -0,0 +1,3 @@
+module github.com/GoogleContainerTools/skaffold/examples/generate-pipeline
+
+go 1.18
diff --git a/examples/getting-started-kustomize/app/Dockerfile b/examples/getting-started-kustomize/app/Dockerfile
index 19becf992..c01cebfca 100644
--- a/examples/getting-started-kustomize/app/Dockerfile
+++ b/examples/getting-started-kustomize/app/Dockerfile
@@ -1,8 +1,10 @@
-FROM golang:1.15 as builder
+FROM golang:1.18 as builder
+WORKDIR /code
 COPY main.go .
+COPY go.mod .
 # `skaffold debug` sets SKAFFOLD_GO_GCFLAGS to disable compiler optimizations
 ARG SKAFFOLD_GO_GCFLAGS
-RUN go build -gcflags="${SKAFFOLD_GO_GCFLAGS}" -o /app main.go
+RUN go build -gcflags="${SKAFFOLD_GO_GCFLAGS}" -trimpath -o /app main.go
 
 FROM alpine:3.10
 # Define GOTRACEBACK to mark this container as using the Go language runtime
diff --git a/examples/getting-started-kustomize/app/go.mod b/examples/getting-started-kustomize/app/go.mod
new file mode 100644
index 000000000..e361158c8
--- /dev/null
+++ b/examples/getting-started-kustomize/app/go.mod
@@ -0,0 +1,3 @@
+module github.com/GoogleContainerTools/skaffold/examples/getting-started-kustomize
+
+go 1.18
diff --git a/examples/getting-started/Dockerfile b/examples/getting-started/Dockerfile
index 415a8456f..67f8c3cbb 100644
--- a/examples/getting-started/Dockerfile
+++ b/examples/getting-started/Dockerfile
@@ -1,8 +1,10 @@
-FROM golang:1.15 as builder
+FROM golang:1.18 as builder
+WORKDIR /code
 COPY main.go .
+COPY go.mod .
 # `skaffold debug` sets SKAFFOLD_GO_GCFLAGS to disable compiler optimizations
 ARG SKAFFOLD_GO_GCFLAGS
-RUN go build -gcflags="${SKAFFOLD_GO_GCFLAGS}" -o /app main.go
+RUN go build -gcflags="${SKAFFOLD_GO_GCFLAGS}" -trimpath -o /app main.go
 
 FROM alpine:3
 # Define GOTRACEBACK to mark this container as using the Go language runtime
diff --git a/examples/getting-started/go.mod b/examples/getting-started/go.mod
new file mode 100644
index 000000000..bc78b13a6
--- /dev/null
+++ b/examples/getting-started/go.mod
@@ -0,0 +1,3 @@
+module github.com/GoogleContainerTools/skaffold/examples/getting-started
+
+go 1.18
diff --git a/examples/google-cloud-build/Dockerfile b/examples/google-cloud-build/Dockerfile
index 415a8456f..67f8c3cbb 100644
--- a/examples/google-cloud-build/Dockerfile
+++ b/examples/google-cloud-build/Dockerfile
@@ -1,8 +1,10 @@
-FROM golang:1.15 as builder
+FROM golang:1.18 as builder
+WORKDIR /code
 COPY main.go .
+COPY go.mod .
 # `skaffold debug` sets SKAFFOLD_GO_GCFLAGS to disable compiler optimizations
 ARG SKAFFOLD_GO_GCFLAGS
-RUN go build -gcflags="${SKAFFOLD_GO_GCFLAGS}" -o /app main.go
+RUN go build -gcflags="${SKAFFOLD_GO_GCFLAGS}" -trimpath -o /app main.go
 
 FROM alpine:3
 # Define GOTRACEBACK to mark this container as using the Go language runtime
diff --git a/examples/google-cloud-build/go.mod b/examples/google-cloud-build/go.mod
new file mode 100644
index 000000000..adee4fb0a
--- /dev/null
+++ b/examples/google-cloud-build/go.mod
@@ -0,0 +1,3 @@
+module github.com/GoogleContainerTools/skaffold/examples/google-cloud-build
+
+go 1.18
diff --git a/examples/helm-deployment/Dockerfile b/examples/helm-deployment/Dockerfile
index 19becf992..c01cebfca 100644
--- a/examples/helm-deployment/Dockerfile
+++ b/examples/helm-deployment/Dockerfile
@@ -1,8 +1,10 @@
-FROM golang:1.15 as builder
+FROM golang:1.18 as builder
+WORKDIR /code
 COPY main.go .
+COPY go.mod .
 # `skaffold debug` sets SKAFFOLD_GO_GCFLAGS to disable compiler optimizations
 ARG SKAFFOLD_GO_GCFLAGS
-RUN go build -gcflags="${SKAFFOLD_GO_GCFLAGS}" -o /app main.go
+RUN go build -gcflags="${SKAFFOLD_GO_GCFLAGS}" -trimpath -o /app main.go
 
 FROM alpine:3.10
 # Define GOTRACEBACK to mark this container as using the Go language runtime
diff --git a/examples/helm-deployment/go.mod b/examples/helm-deployment/go.mod
new file mode 100644
index 000000000..6d6d3ddc5
--- /dev/null
+++ b/examples/helm-deployment/go.mod
@@ -0,0 +1,3 @@
+module github.com/GoogleContainerTools/skaffold/examples/helm-deployment
+
+go 1.18
diff --git a/examples/kaniko/Dockerfile b/examples/kaniko/Dockerfile
index 415a8456f..67f8c3cbb 100644
--- a/examples/kaniko/Dockerfile
+++ b/examples/kaniko/Dockerfile
@@ -1,8 +1,10 @@
-FROM golang:1.15 as builder
+FROM golang:1.18 as builder
+WORKDIR /code
 COPY main.go .
+COPY go.mod .
 # `skaffold debug` sets SKAFFOLD_GO_GCFLAGS to disable compiler optimizations
 ARG SKAFFOLD_GO_GCFLAGS
-RUN go build -gcflags="${SKAFFOLD_GO_GCFLAGS}" -o /app main.go
+RUN go build -gcflags="${SKAFFOLD_GO_GCFLAGS}" -trimpath -o /app main.go
 
 FROM alpine:3
 # Define GOTRACEBACK to mark this container as using the Go language runtime
diff --git a/examples/kaniko/go.mod b/examples/kaniko/go.mod
new file mode 100644
index 000000000..256a942c9
--- /dev/null
+++ b/examples/kaniko/go.mod
@@ -0,0 +1,3 @@
+module github.com/GoogleContainerTools/skaffold/examples/kaniko
+
+go 1.18
diff --git a/examples/lifecycle-hooks/Dockerfile b/examples/lifecycle-hooks/Dockerfile
index 58c97be53..3b7221254 100644
--- a/examples/lifecycle-hooks/Dockerfile
+++ b/examples/lifecycle-hooks/Dockerfile
@@ -1,8 +1,10 @@
-FROM golang:1.15 as builder
+FROM golang:1.18 as builder
+WORKDIR /code
 COPY main.go .
+COPY go.mod .
 # `skaffold debug` sets SKAFFOLD_GO_GCFLAGS to disable compiler optimizations
 ARG SKAFFOLD_GO_GCFLAGS
-RUN go build -gcflags="${SKAFFOLD_GO_GCFLAGS}" -o /app main.go
+RUN go build -gcflags="${SKAFFOLD_GO_GCFLAGS}" -trimpath -o /app main.go
 
 FROM alpine:3
 # Define GOTRACEBACK to mark this container as using the Go language runtime
diff --git a/examples/lifecycle-hooks/go.mod b/examples/lifecycle-hooks/go.mod
new file mode 100644
index 000000000..d33ad1977
--- /dev/null
+++ b/examples/lifecycle-hooks/go.mod
@@ -0,0 +1,3 @@
+module github.com/GoogleContainerTools/skaffold/examples/lifecycle-hooks
+
+go 1.18
diff --git a/examples/microservices/leeroy-app/Dockerfile b/examples/microservices/leeroy-app/Dockerfile
index 4403c2637..6d57aae25 100644
--- a/examples/microservices/leeroy-app/Dockerfile
+++ b/examples/microservices/leeroy-app/Dockerfile
@@ -1,9 +1,11 @@
 ARG BASE
-FROM golang:1.15 as builder
+FROM golang:1.18 as builder
+WORKDIR /code
 COPY app.go .
+COPY go.mod .
 # `skaffold debug` sets SKAFFOLD_GO_GCFLAGS to disable compiler optimizations
 ARG SKAFFOLD_GO_GCFLAGS
-RUN go build -gcflags="${SKAFFOLD_GO_GCFLAGS}" -o /app .
+RUN go build -gcflags="${SKAFFOLD_GO_GCFLAGS}" -trimpath -o /app .
 
 FROM $BASE
 COPY --from=builder /app .
diff --git a/examples/microservices/leeroy-app/go.mod b/examples/microservices/leeroy-app/go.mod
new file mode 100644
index 000000000..0805e1627
--- /dev/null
+++ b/examples/microservices/leeroy-app/go.mod
@@ -0,0 +1,3 @@
+module github.com/GoogleContainerTools/skaffold/examples/microservices/leeroy-app
+
+go 1.18
diff --git a/examples/microservices/leeroy-web/Dockerfile b/examples/microservices/leeroy-web/Dockerfile
index 122977c1c..83e4a2e28 100644
--- a/examples/microservices/leeroy-web/Dockerfile
+++ b/examples/microservices/leeroy-web/Dockerfile
@@ -1,9 +1,11 @@
 ARG BASE
-FROM golang:1.15 as builder
+FROM golang:1.18 as builder
+WORKDIR /code
 COPY web.go .
+COPY go.mod .
 # `skaffold debug` sets SKAFFOLD_GO_GCFLAGS to disable compiler optimizations
 ARG SKAFFOLD_GO_GCFLAGS
-RUN go build -gcflags="${SKAFFOLD_GO_GCFLAGS}" -o /app .
+RUN go build -gcflags="${SKAFFOLD_GO_GCFLAGS}" -trimpath -o /app .
 
 FROM $BASE
 COPY --from=builder /app .
diff --git a/examples/microservices/leeroy-web/go.mod b/examples/microservices/leeroy-web/go.mod
new file mode 100644
index 000000000..cd5771064
--- /dev/null
+++ b/examples/microservices/leeroy-web/go.mod
@@ -0,0 +1,3 @@
+module github.com/GoogleContainerTools/skaffold/examples/microservices/leeroy-web
+
+go 1.18
diff --git a/examples/multi-config-microservices/leeroy-app/Dockerfile b/examples/multi-config-microservices/leeroy-app/Dockerfile
index 4403c2637..6d57aae25 100644
--- a/examples/multi-config-microservices/leeroy-app/Dockerfile
+++ b/examples/multi-config-microservices/leeroy-app/Dockerfile
@@ -1,9 +1,11 @@
 ARG BASE
-FROM golang:1.15 as builder
+FROM golang:1.18 as builder
+WORKDIR /code
 COPY app.go .
+COPY go.mod .
 # `skaffold debug` sets SKAFFOLD_GO_GCFLAGS to disable compiler optimizations
 ARG SKAFFOLD_GO_GCFLAGS
-RUN go build -gcflags="${SKAFFOLD_GO_GCFLAGS}" -o /app .
+RUN go build -gcflags="${SKAFFOLD_GO_GCFLAGS}" -trimpath -o /app .
 
 FROM $BASE
 COPY --from=builder /app .
diff --git a/examples/multi-config-microservices/leeroy-app/go.mod b/examples/multi-config-microservices/leeroy-app/go.mod
new file mode 100644
index 000000000..f8b73a7f0
--- /dev/null
+++ b/examples/multi-config-microservices/leeroy-app/go.mod
@@ -0,0 +1,3 @@
+module github.com/GoogleContainerTools/skaffold/examples/multi-config-microservices/leeroy-app
+
+go 1.18
diff --git a/examples/multi-config-microservices/leeroy-web/Dockerfile b/examples/multi-config-microservices/leeroy-web/Dockerfile
index 122977c1c..83e4a2e28 100644
--- a/examples/multi-config-microservices/leeroy-web/Dockerfile
+++ b/examples/multi-config-microservices/leeroy-web/Dockerfile
@@ -1,9 +1,11 @@
 ARG BASE
-FROM golang:1.15 as builder
+FROM golang:1.18 as builder
+WORKDIR /code
 COPY web.go .
+COPY go.mod .
 # `skaffold debug` sets SKAFFOLD_GO_GCFLAGS to disable compiler optimizations
 ARG SKAFFOLD_GO_GCFLAGS
-RUN go build -gcflags="${SKAFFOLD_GO_GCFLAGS}" -o /app .
+RUN go build -gcflags="${SKAFFOLD_GO_GCFLAGS}" -trimpath -o /app .
 
 FROM $BASE
 COPY --from=builder /app .
diff --git a/examples/multi-config-microservices/leeroy-web/go.mod b/examples/multi-config-microservices/leeroy-web/go.mod
new file mode 100644
index 000000000..6aa40ce02
--- /dev/null
+++ b/examples/multi-config-microservices/leeroy-web/go.mod
@@ -0,0 +1,3 @@
+module github.com/GoogleContainerTools/skaffold/examples/multi-config-microservices/leeroy-web
+
+go 1.18
diff --git a/examples/profile-patches/base-service/Dockerfile b/examples/profile-patches/base-service/Dockerfile
index 415a8456f..67f8c3cbb 100644
--- a/examples/profile-patches/base-service/Dockerfile
+++ b/examples/profile-patches/base-service/Dockerfile
@@ -1,8 +1,10 @@
-FROM golang:1.15 as builder
+FROM golang:1.18 as builder
+WORKDIR /code
 COPY main.go .
+COPY go.mod .
 # `skaffold debug` sets SKAFFOLD_GO_GCFLAGS to disable compiler optimizations
 ARG SKAFFOLD_GO_GCFLAGS
-RUN go build -gcflags="${SKAFFOLD_GO_GCFLAGS}" -o /app main.go
+RUN go build -gcflags="${SKAFFOLD_GO_GCFLAGS}" -trimpath -o /app main.go
 
 FROM alpine:3
 # Define GOTRACEBACK to mark this container as using the Go language runtime
diff --git a/examples/profile-patches/base-service/go.mod b/examples/profile-patches/base-service/go.mod
new file mode 100644
index 000000000..ec9a5a76e
--- /dev/null
+++ b/examples/profile-patches/base-service/go.mod
@@ -0,0 +1,3 @@
+module github.com/GoogleContainerTools/skaffold/examples/profile-patches/base-service
+
+go 1.18
diff --git a/examples/profile-patches/hello-service/Dockerfile b/examples/profile-patches/hello-service/Dockerfile
index 415a8456f..67f8c3cbb 100644
--- a/examples/profile-patches/hello-service/Dockerfile
+++ b/examples/profile-patches/hello-service/Dockerfile
@@ -1,8 +1,10 @@
-FROM golang:1.15 as builder
+FROM golang:1.18 as builder
+WORKDIR /code
 COPY main.go .
+COPY go.mod .
 # `skaffold debug` sets SKAFFOLD_GO_GCFLAGS to disable compiler optimizations
 ARG SKAFFOLD_GO_GCFLAGS
-RUN go build -gcflags="${SKAFFOLD_GO_GCFLAGS}" -o /app main.go
+RUN go build -gcflags="${SKAFFOLD_GO_GCFLAGS}" -trimpath -o /app main.go
 
 FROM alpine:3
 # Define GOTRACEBACK to mark this container as using the Go language runtime
diff --git a/examples/profile-patches/hello-service/go.mod b/examples/profile-patches/hello-service/go.mod
new file mode 100644
index 000000000..c8d36c082
--- /dev/null
+++ b/examples/profile-patches/hello-service/go.mod
@@ -0,0 +1,3 @@
+module github.com/GoogleContainerTools/skaffold/examples/profile-patches/hello-service
+
+go 1.18
diff --git a/examples/profile-patches/world-service/Dockerfile b/examples/profile-patches/world-service/Dockerfile
index 415a8456f..67f8c3cbb 100644
--- a/examples/profile-patches/world-service/Dockerfile
+++ b/examples/profile-patches/world-service/Dockerfile
@@ -1,8 +1,10 @@
-FROM golang:1.15 as builder
+FROM golang:1.18 as builder
+WORKDIR /code
 COPY main.go .
+COPY go.mod .
 # `skaffold debug` sets SKAFFOLD_GO_GCFLAGS to disable compiler optimizations
 ARG SKAFFOLD_GO_GCFLAGS
-RUN go build -gcflags="${SKAFFOLD_GO_GCFLAGS}" -o /app main.go
+RUN go build -gcflags="${SKAFFOLD_GO_GCFLAGS}" -trimpath -o /app main.go
 
 FROM alpine:3
 # Define GOTRACEBACK to mark this container as using the Go language runtime
diff --git a/examples/profile-patches/world-service/go.mod b/examples/profile-patches/world-service/go.mod
new file mode 100644
index 000000000..b36efec6e
--- /dev/null
+++ b/examples/profile-patches/world-service/go.mod
@@ -0,0 +1,3 @@
+module github.com/GoogleContainerTools/skaffold/examples/profile-patches/world-service
+
+go 1.18
diff --git a/examples/profiles/hello-service/Dockerfile b/examples/profiles/hello-service/Dockerfile
index 415a8456f..67f8c3cbb 100644
--- a/examples/profiles/hello-service/Dockerfile
+++ b/examples/profiles/hello-service/Dockerfile
@@ -1,8 +1,10 @@
-FROM golang:1.15 as builder
+FROM golang:1.18 as builder
+WORKDIR /code
 COPY main.go .
+COPY go.mod .
 # `skaffold debug` sets SKAFFOLD_GO_GCFLAGS to disable compiler optimizations
 ARG SKAFFOLD_GO_GCFLAGS
-RUN go build -gcflags="${SKAFFOLD_GO_GCFLAGS}" -o /app main.go
+RUN go build -gcflags="${SKAFFOLD_GO_GCFLAGS}" -trimpath -o /app main.go
 
 FROM alpine:3
 # Define GOTRACEBACK to mark this container as using the Go language runtime
diff --git a/examples/profiles/hello-service/go.mod b/examples/profiles/hello-service/go.mod
new file mode 100644
index 000000000..a98e5c4ba
--- /dev/null
+++ b/examples/profiles/hello-service/go.mod
@@ -0,0 +1,3 @@
+module github.com/GoogleContainerTools/skaffold/examples/profiles/hello-service
+
+go 1.18
diff --git a/examples/profiles/world-service/Dockerfile b/examples/profiles/world-service/Dockerfile
index 415a8456f..67f8c3cbb 100644
--- a/examples/profiles/world-service/Dockerfile
+++ b/examples/profiles/world-service/Dockerfile
@@ -1,8 +1,10 @@
-FROM golang:1.15 as builder
+FROM golang:1.18 as builder
+WORKDIR /code
 COPY main.go .
+COPY go.mod .
 # `skaffold debug` sets SKAFFOLD_GO_GCFLAGS to disable compiler optimizations
 ARG SKAFFOLD_GO_GCFLAGS
-RUN go build -gcflags="${SKAFFOLD_GO_GCFLAGS}" -o /app main.go
+RUN go build -gcflags="${SKAFFOLD_GO_GCFLAGS}" -trimpath -o /app main.go
 
 FROM alpine:3
 # Define GOTRACEBACK to mark this container as using the Go language runtime
diff --git a/examples/profiles/world-service/go.mod b/examples/profiles/world-service/go.mod
new file mode 100644
index 000000000..e87efb5ec
--- /dev/null
+++ b/examples/profiles/world-service/go.mod
@@ -0,0 +1,3 @@
+module github.com/GoogleContainerTools/skaffold/examples/profiles/world-service
+
+go 1.18
diff --git a/examples/simple-artifact-dependency/app/Dockerfile b/examples/simple-artifact-dependency/app/Dockerfile
index 237eace01..57dfed7ce 100644
--- a/examples/simple-artifact-dependency/app/Dockerfile
+++ b/examples/simple-artifact-dependency/app/Dockerfile
@@ -1,9 +1,11 @@
 ARG BASE
-FROM golang:1.15 as builder
+FROM golang:1.18 as builder
+WORKDIR /code
 COPY main.go .
+COPY go.mod .
 # `skaffold debug` sets SKAFFOLD_GO_GCFLAGS to disable compiler optimizations
 ARG SKAFFOLD_GO_GCFLAGS
-RUN go build -gcflags="${SKAFFOLD_GO_GCFLAGS}" -o /app .
+RUN go build -gcflags="${SKAFFOLD_GO_GCFLAGS}" -trimpath -o /app .
 
 FROM $BASE
 # Define GOTRACEBACK to mark this container as using the Go language runtime
diff --git a/examples/simple-artifact-dependency/app/go.mod b/examples/simple-artifact-dependency/app/go.mod
new file mode 100644
index 000000000..e3cfc6875
--- /dev/null
+++ b/examples/simple-artifact-dependency/app/go.mod
@@ -0,0 +1,3 @@
+module github.com/GoogleContainerTools/skaffold/examples/simple-artifact-dependency
+
+go 1.18
diff --git a/examples/tagging-with-environment-variables/Dockerfile b/examples/tagging-with-environment-variables/Dockerfile
index 415a8456f..67f8c3cbb 100644
--- a/examples/tagging-with-environment-variables/Dockerfile
+++ b/examples/tagging-with-environment-variables/Dockerfile
@@ -1,8 +1,10 @@
-FROM golang:1.15 as builder
+FROM golang:1.18 as builder
+WORKDIR /code
 COPY main.go .
+COPY go.mod .
 # `skaffold debug` sets SKAFFOLD_GO_GCFLAGS to disable compiler optimizations
 ARG SKAFFOLD_GO_GCFLAGS
-RUN go build -gcflags="${SKAFFOLD_GO_GCFLAGS}" -o /app main.go
+RUN go build -gcflags="${SKAFFOLD_GO_GCFLAGS}" -trimpath -o /app main.go
 
 FROM alpine:3
 # Define GOTRACEBACK to mark this container as using the Go language runtime
diff --git a/examples/tagging-with-environment-variables/go.mod b/examples/tagging-with-environment-variables/go.mod
new file mode 100644
index 000000000..bd6fb8b17
--- /dev/null
+++ b/examples/tagging-with-environment-variables/go.mod
@@ -0,0 +1,3 @@
+module github.com/GoogleContainerTools/skaffold/examples/tagging-with-environment-variable
+
+go 1.18
diff --git a/pkg/skaffold/build/ko/init/init.go b/pkg/skaffold/build/ko/init/init.go
new file mode 100644
index 000000000..98ecafbf9
--- /dev/null
+++ b/pkg/skaffold/build/ko/init/init.go
@@ -0,0 +1,70 @@
+/*
+Copyright 2022 The Skaffold Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package init
+
+import (
+	"fmt"
+	"path/filepath"
+
+	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/initializer/build"
+	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest"
+)
+
+// ArtifactConfig holds information about a Ko project
+type ArtifactConfig struct {
+	File string `json:"path,omitempty"`
+}
+
+var _ build.InitBuilder = &ArtifactConfig{}
+
+// Validate checks if the file is a Go module file.
+func Validate(path string) bool {
+	return filepath.Base(path) == "go.mod"
+}
+
+// Name returns the name of the builder.
+func (c ArtifactConfig) Name() string {
+	return "Ko"
+}
+
+// Describe returns the initBuilder's string representation.
+// This representation is used when prompting the user to choose a builder.
+func (c ArtifactConfig) Describe() string {
+	return fmt.Sprintf("%s (%s)", c.Name(), c.File)
+}
+
+// ArtifactType returns a definition of the artifact to be built.
+func (c ArtifactConfig) ArtifactType(_ string) latest.ArtifactType {
+	return latest.ArtifactType{
+		KoArtifact: &latest.KoArtifact{
+			Dependencies: &latest.KoDependencies{
+				Paths: []string{"**/*.go", "go.*"},
+			},
+		},
+	}
+}
+
+// ConfiguredImage returns the target image configured by the builder, or empty string if no image is configured.
+func (c ArtifactConfig) ConfiguredImage() string {
+	// Target image is not configured in Ko.
+	return ""
+}
+
+// Path returns the path to the build definition.
+func (c ArtifactConfig) Path() string {
+	return c.File
+}
diff --git a/pkg/skaffold/deploy/cloudrun/accessor.go b/pkg/skaffold/deploy/cloudrun/accessor.go
index a53911b3d..95a1878ca 100644
--- a/pkg/skaffold/deploy/cloudrun/accessor.go
+++ b/pkg/skaffold/deploy/cloudrun/accessor.go
@@ -19,6 +19,7 @@ import (
 	"context"
 	"fmt"
 	"io"
+	"os"
 	"os/exec"
 	"strconv"
 
@@ -40,13 +41,14 @@ var (
 )
 
 type resourceTracker struct {
-	resources          []forwardedResource
+	resources          map[RunResourceName]*forwardedResource
 	configuredForwards []*latest.PortForwardResource
 	forwardedPorts     *util.PortSet
 }
 
 type forwardedResource struct {
 	name    RunResourceName
+	cmd     *exec.Cmd
 	cancel  context.CancelFunc
 	started bool
 	port    int
@@ -59,7 +61,7 @@ type forwarder interface {
 }
 
 // RunAccessor is an access.Accessor for Cloud Run resources
-// It uses `gcloud run proxy``to enable port forwarding for Cloud Run. This makes it easier to call IAM-protected Cloud Run services
+// It uses `gcloud run proxy“to enable port forwarding for Cloud Run. This makes it easier to call IAM-protected Cloud Run services
 // by going through localhost. In order to set up forwarding, the services must have their ingress setting set to "all", gcloud  must be
 // installed and on the path, and the currently configured gcloud user has run.services.invoke permission on the services being proxied
 type RunAccessor struct {
@@ -84,13 +86,21 @@ func NewAccessor(cfg Config, label string) *RunAccessor {
 
 // AddResource tracks an additional resource to port forward
 func (r *RunAccessor) AddResource(resource RunResourceName) {
+	if r.resources.resources == nil {
+		r.resources.resources = make(map[RunResourceName]*forwardedResource)
+	}
 	port := 0
 	for _, forward := range r.resources.configuredForwards {
 		if forward.Type == "service" && forward.Name == resource.Service {
 			port = forward.LocalPort
 		}
 	}
-	r.resources.resources = append(r.resources.resources, forwardedResource{name: resource, started: false, port: port})
+	if _, present := r.resources.resources[resource]; !present {
+		r.resources.resources[resource] = &forwardedResource{name: resource, started: false, port: port}
+	} else {
+		// signal that we need to start a new forward for this resource
+		r.resources.resources[resource].started = false
+	}
 }
 
 // Start begins port forwarding for the tracked Cloud Run services.
@@ -134,6 +144,10 @@ func (r *runProxyForwarder) Start(ctx context.Context, out io.Writer) error {
 		output.Red.Fprintln(out, "gcloud not found on path. Unable to set up Cloud Run port forwarding")
 		return sErrors.NewError(fmt.Errorf("gcloud not found"), &proto.ActionableErr{ErrCode: proto.StatusCode_PORT_FORWARD_RUN_GCLOUD_NOT_FOUND})
 	}
+	if r.resources.resources == nil {
+		// no forwards configured
+		return nil
+	}
 	for _, resource := range r.resources.resources {
 		if resource.port == 0 {
 			port := retrieveAvailablePort("localhost", 8080, r.resources.forwardedPorts)
@@ -155,7 +169,6 @@ func (r *runProxyForwarder) Start(ctx context.Context, out io.Writer) error {
 			go func() {
 				err := cmd.Wait()
 				if err != nil {
-					output.Red.Fprintf(out, "Port forward of %s quit unsuccessfuly: %v", resource.name.String(), err)
 					eventV2.TaskFailed(constants.PortForward, err)
 				} else {
 					eventV2.TaskSucceeded(constants.PortForward)
@@ -163,6 +176,7 @@ func (r *runProxyForwarder) Start(ctx context.Context, out io.Writer) error {
 			}()
 			eventV2.PortForwarded(int32(resource.port), schemautil.FromInt(443), "", "", resource.name.Project, "", "run-service", resource.name.Service, resource.name.String())
 			resource.started = true
+			resource.cmd = cmd
 		}
 	}
 	return nil
@@ -176,9 +190,17 @@ func getGcloudProxyArgs(resource RunResourceName, port int) []string {
 func (r *runProxyForwarder) Stop() {
 	for _, resource := range r.resources.resources {
 		if resource.cancel != nil {
-			resource.cancel()
+			if resource.cmd != nil {
+				if err := resource.cmd.Process.Signal(os.Interrupt); err != nil {
+					// signaling didn't work, force cancel
+					resource.cancel()
+				}
+			} else {
+				// we don't have a command, force cancel the context.
+				resource.cancel()
+			}
 			resource.cancel = nil
-			resource.started = false
+			resource.cmd = nil
 		}
 	}
 }
diff --git a/pkg/skaffold/deploy/cloudrun/deploy.go b/pkg/skaffold/deploy/cloudrun/deploy.go
index 2ca2ed399..949e09e28 100644
--- a/pkg/skaffold/deploy/cloudrun/deploy.go
+++ b/pkg/skaffold/deploy/cloudrun/deploy.go
@@ -101,8 +101,8 @@ func (d *Deployer) Dependencies() ([]string, error) {
 }
 
 // Cleanup deletes the created Cloud Run services
-func (d *Deployer) Cleanup(ctx context.Context, out io.Writer, dryRun bool, manifests manifest.ManifestList) error {
-	return d.deleteRunService(ctx, out, dryRun, manifests)
+func (d *Deployer) Cleanup(ctx context.Context, out io.Writer, dryRun bool, byConfig *manifest.ManifestListByConfig) error {
+	return d.deleteRunService(ctx, out, dryRun, byConfig.GetForConfig(d.configName))
 }
 
 // GetDebugger Get the Debugger for Cloud Run. Not supported by this deployer.
diff --git a/pkg/skaffold/deploy/deploy.go b/pkg/skaffold/deploy/deploy.go
index bae499f47..3456796e1 100644
--- a/pkg/skaffold/deploy/deploy.go
+++ b/pkg/skaffold/deploy/deploy.go
@@ -41,7 +41,7 @@ type Deployer interface {
 	Dependencies() ([]string, error)
 
 	// Cleanup deletes what was deployed by calling Deploy.
-	Cleanup(context.Context, io.Writer, bool, manifest.ManifestList) error
+	Cleanup(context.Context, io.Writer, bool, *manifest.ManifestListByConfig) error
 
 	// GetDebugger returns a Deployer's implementation of a Debugger
 	GetDebugger() debug.Debugger
diff --git a/pkg/skaffold/deploy/deploy_mux.go b/pkg/skaffold/deploy/deploy_mux.go
index 13b19ea28..953314b07 100644
--- a/pkg/skaffold/deploy/deploy_mux.go
+++ b/pkg/skaffold/deploy/deploy_mux.go
@@ -160,13 +160,13 @@ func (m DeployerMux) Dependencies() ([]string, error) {
 	return deps.ToList(), nil
 }
 
-func (m DeployerMux) Cleanup(ctx context.Context, w io.Writer, dryRun bool, list manifest.ManifestList) error {
+func (m DeployerMux) Cleanup(ctx context.Context, w io.Writer, dryRun bool, manifestsByConfig *manifest.ManifestListByConfig) error {
 	for _, deployer := range m.deployers {
 		ctx, endTrace := instrumentation.StartTrace(ctx, "Cleanup")
 		if dryRun {
 			output.Yellow.Fprintln(w, "Following resources would be deleted:")
 		}
-		if err := deployer.Cleanup(ctx, w, dryRun, list); err != nil {
+		if err := deployer.Cleanup(ctx, w, dryRun, manifestsByConfig); err != nil {
 			return err
 		}
 		endTrace()
diff --git a/pkg/skaffold/deploy/docker/deploy.go b/pkg/skaffold/deploy/docker/deploy.go
index b351448d1..1226eb069 100644
--- a/pkg/skaffold/deploy/docker/deploy.go
+++ b/pkg/skaffold/deploy/docker/deploy.go
@@ -295,7 +295,7 @@ func (d *Deployer) Dependencies() ([]string, error) {
 	return nil, nil
 }
 
-func (d *Deployer) Cleanup(ctx context.Context, out io.Writer, dryRun bool, list manifest.ManifestList) error {
+func (d *Deployer) Cleanup(ctx context.Context, out io.Writer, dryRun bool, _ *manifest.ManifestListByConfig) error {
 	if dryRun {
 		for _, container := range d.tracker.DeployedContainers() {
 			output.Yellow.Fprintln(out, container.ID)
diff --git a/pkg/skaffold/deploy/helm/helm.go b/pkg/skaffold/deploy/helm/helm.go
index fa10f3600..9dbb6c0d4 100644
--- a/pkg/skaffold/deploy/helm/helm.go
+++ b/pkg/skaffold/deploy/helm/helm.go
@@ -355,7 +355,7 @@ func (h *Deployer) Dependencies() ([]string, error) {
 }
 
 // Cleanup deletes what was deployed by calling Deploy.
-func (h *Deployer) Cleanup(ctx context.Context, out io.Writer, dryRun bool, list manifest.ManifestList) error {
+func (h *Deployer) Cleanup(ctx context.Context, out io.Writer, dryRun bool, _ *manifest.ManifestListByConfig) error {
 	instrumentation.AddAttributesToCurrentSpanFromContext(ctx, map[string]string{
 		"DeployerType": "helm",
 	})
diff --git a/pkg/skaffold/deploy/kpt/kpt.go b/pkg/skaffold/deploy/kpt/kpt.go
index f2c9b0ed0..d0e50bc96 100644
--- a/pkg/skaffold/deploy/kpt/kpt.go
+++ b/pkg/skaffold/deploy/kpt/kpt.go
@@ -367,7 +367,7 @@ func (k *Deployer) Dependencies() ([]string, error) {
 }
 
 // Cleanup deletes what was deployed by calling `kpt live destroy`.
-func (k *Deployer) Cleanup(ctx context.Context, out io.Writer, dryRun bool, list manifest.ManifestList) error {
+func (k *Deployer) Cleanup(ctx context.Context, out io.Writer, dryRun bool, _ *manifest.ManifestListByConfig) error {
 	instrumentation.AddAttributesToCurrentSpanFromContext(ctx, map[string]string{
 		"DeployerType": deployerName,
 	})
diff --git a/pkg/skaffold/deploy/kubectl/errors.go b/pkg/skaffold/deploy/kubectl/errors.go
index 45ce01a7a..5b3f35bbe 100644
--- a/pkg/skaffold/deploy/kubectl/errors.go
+++ b/pkg/skaffold/deploy/kubectl/errors.go
@@ -43,20 +43,6 @@ func versionGetErr(err error) error {
 		})
 }
 
-func offlineModeErr() error {
-	return sErrors.NewErrorWithStatusCode(
-		&proto.ActionableErr{
-			Message: "Cannot use offline mode if URL manifests are configured",
-			ErrCode: proto.StatusCode_DEPLOY_KUBECTL_OFFLINE_MODE_ERR,
-			Suggestions: []*proto.Suggestion{
-				{
-					SuggestionCode: proto.SuggestionCode_SET_RENDER_FLAG_OFFLINE_FALSE,
-					Action:         "Please rerun with --offline=false",
-				},
-			},
-		})
-}
-
 func waitForDeletionErr(err error) error {
 	return sErrors.NewError(err,
 		&proto.ActionableErr{
diff --git a/pkg/skaffold/deploy/kubectl/kubectl.go b/pkg/skaffold/deploy/kubectl/kubectl.go
index fd4f44c25..96de62064 100644
--- a/pkg/skaffold/deploy/kubectl/kubectl.go
+++ b/pkg/skaffold/deploy/kubectl/kubectl.go
@@ -21,7 +21,6 @@ import (
 	"context"
 	"fmt"
 	"io"
-	"os"
 	"strings"
 
 	"github.com/segmentio/textio"
@@ -328,50 +327,6 @@ func (k *Deployer) manifestFiles(manifests []string) ([]string, error) {
 	return filteredManifests, nil
 }
 
-// readManifests reads the manifests to deploy/delete.
-func (k *Deployer) readManifests(ctx context.Context, offline bool) (manifest.ManifestList, error) {
-	var manifests []string
-
-	// Clean the temporary directory that holds the manifests downloaded from GCS
-	defer os.RemoveAll(k.gcsManifestDir)
-
-	// Append URL manifests. URL manifests are excluded from `Dependencies`.
-	hasURLManifest := false
-	for _, manifest := range k.KubectlDeploy.Manifests {
-		if util.IsURL(manifest) {
-			manifests = append(manifests, manifest)
-			hasURLManifest = true
-		}
-	}
-
-	if len(manifests) == 0 {
-		return manifest.ManifestList{}, nil
-	}
-
-	if !offline {
-		return k.kubectl.ReadManifests(ctx, manifests)
-	}
-
-	// In case no URLs are provided, we can stay offline - no need to run "kubectl create" which
-	// would try to connect to a cluster (https://github.com/kubernetes/kubernetes/issues/51475)
-	if hasURLManifest {
-		return nil, offlineModeErr()
-	}
-	return createManifestList(manifests)
-}
-
-func createManifestList(manifests []string) (manifest.ManifestList, error) {
-	var manifestList manifest.ManifestList
-	for _, manifestFilePath := range manifests {
-		manifestFileContent, err := os.ReadFile(manifestFilePath)
-		if err != nil {
-			return nil, readManifestErr(fmt.Errorf("reading manifest file %v: %w", manifestFilePath, err))
-		}
-		manifestList.Append(manifestFileContent)
-	}
-	return manifestList, nil
-}
-
 // readRemoteManifests will try to read manifests from the given kubernetes
 // context in the specified namespace and for the specified type
 func (k *Deployer) readRemoteManifest(ctx context.Context, name string) ([]byte, error) {
@@ -393,15 +348,14 @@ func (k *Deployer) readRemoteManifest(ctx context.Context, name string) ([]byte,
 }
 
 // Cleanup deletes what was deployed by calling Deploy.
-func (k *Deployer) Cleanup(ctx context.Context, out io.Writer, dryRun bool, manifests manifest.ManifestList) error {
+func (k *Deployer) Cleanup(ctx context.Context, out io.Writer, dryRun bool, manifestsByConfig *manifest.ManifestListByConfig) error {
+	var manifests manifest.ManifestList
+	if manifestsByConfig != nil {
+		manifests = append(manifests, manifestsByConfig.GetForConfig(k.ConfigName())...)
+	}
 	instrumentation.AddAttributesToCurrentSpanFromContext(ctx, map[string]string{
 		"DeployerType": "kubectl",
 	})
-	other, err := k.readManifests(ctx, false)
-	manifests = append(manifests, other...)
-	if err != nil {
-		return err
-	}
 	if dryRun {
 		for _, manifest := range manifests {
 			output.White.Fprintf(out, "---\n%s", manifest)
@@ -411,6 +365,7 @@ func (k *Deployer) Cleanup(ctx context.Context, out io.Writer, dryRun bool, mani
 	// revert remote manifests
 	// TODO(dgageot): That seems super dangerous and I don't understand
 	// why we need to update resources just before we delete them.
+	// todo clean up this block as RemoteManifests is no longer used for deployer
 	if len(k.RemoteManifests) > 0 {
 		var rm manifest.ManifestList
 		for _, m := range k.RemoteManifests {
diff --git a/pkg/skaffold/deploy/kustomize/kustomize.go b/pkg/skaffold/deploy/kustomize/kustomize.go
index 2f147a04f..d002cde0b 100644
--- a/pkg/skaffold/deploy/kustomize/kustomize.go
+++ b/pkg/skaffold/deploy/kustomize/kustomize.go
@@ -369,7 +369,7 @@ func (k *Deployer) renderManifests(ctx context.Context, out io.Writer, builds []
 }
 
 // Cleanup deletes what was deployed by calling Deploy.
-func (k *Deployer) Cleanup(ctx context.Context, out io.Writer, dryRun bool, list manifest.ManifestList) error {
+func (k *Deployer) Cleanup(ctx context.Context, out io.Writer, dryRun bool, _ *manifest.ManifestListByConfig) error {
 	instrumentation.AddAttributesToCurrentSpanFromContext(ctx, map[string]string{
 		"DeployerType": "kustomize",
 	})
diff --git a/pkg/skaffold/initializer/analyze/analyze.go b/pkg/skaffold/initializer/analyze/analyze.go
index 8aa55aa01..07e03f662 100644
--- a/pkg/skaffold/initializer/analyze/analyze.go
+++ b/pkg/skaffold/initializer/analyze/analyze.go
@@ -105,6 +105,7 @@ func NewAnalyzer(c config.Config) *ProjectAnalysis {
 			findBuilders:         !c.SkipBuild,
 			enableJibInit:        c.EnableJibInit,
 			enableJibGradleInit:  c.EnableJibGradleInit,
+			enableKoInit:         c.EnableKoInit,
 			enableBuildpacksInit: c.EnableBuildpacksInit,
 			buildpacksBuilder:    c.BuildpacksBuilder,
 		},
diff --git a/pkg/skaffold/initializer/analyze/builder.go b/pkg/skaffold/initializer/analyze/builder.go
index 8db7df081..aa4b1cb8b 100644
--- a/pkg/skaffold/initializer/analyze/builder.go
+++ b/pkg/skaffold/initializer/analyze/builder.go
@@ -23,6 +23,7 @@ import (
 
 	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/build/buildpacks"
 	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/build/jib"
+	koinit "github.com/GoogleContainerTools/skaffold/pkg/skaffold/build/ko/init"
 	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/docker"
 	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/initializer/build"
 )
@@ -31,6 +32,7 @@ type builderAnalyzer struct {
 	directoryAnalyzer
 	enableJibInit        bool
 	enableJibGradleInit  bool
+	enableKoInit         bool
 	enableBuildpacksInit bool
 	findBuilders         bool
 	buildpacksBuilder    string
@@ -86,6 +88,14 @@ func (a *builderAnalyzer) detectBuilders(ctx context.Context, path string, detec
 		}
 	}
 
+	if a.enableKoInit {
+		if koinit.Validate(path) {
+			results = append(results, koinit.ArtifactConfig{
+				File: path,
+			})
+		}
+	}
+
 	// TODO: Remove backwards compatibility if statement (not entire block)
 	if a.enableBuildpacksInit {
 		// Check for buildpacks
diff --git a/pkg/skaffold/initializer/build/resolve.go b/pkg/skaffold/initializer/build/resolve.go
index 567807a8f..ab81a4c9f 100644
--- a/pkg/skaffold/initializer/build/resolve.go
+++ b/pkg/skaffold/initializer/build/resolve.go
@@ -85,13 +85,15 @@ func builderRank(builder InitBuilder) int {
 		return 1
 	case a.JibArtifact != nil:
 		return 2
-	case a.BazelArtifact != nil:
+	case a.KoArtifact != nil:
 		return 3
-	case a.BuildpackArtifact != nil:
+	case a.BazelArtifact != nil:
 		return 4
+	case a.BuildpackArtifact != nil:
+		return 5
 	}
 
-	return 5
+	return 6
 }
 
 func (d *defaultBuildInitializer) resolveBuilderImagesInteractively() error {
diff --git a/pkg/skaffold/initializer/config/config.go b/pkg/skaffold/initializer/config/config.go
index 28eb5435a..1bc1cf553 100644
--- a/pkg/skaffold/initializer/config/config.go
+++ b/pkg/skaffold/initializer/config/config.go
@@ -31,6 +31,7 @@ type Config struct {
 	Analyze                  bool
 	EnableJibInit            bool // TODO: Remove this parameter
 	EnableJibGradleInit      bool
+	EnableKoInit             bool
 	EnableBuildpacksInit     bool
 	EnableNewInitFormat      bool
 	EnableManifestGeneration bool
diff --git a/pkg/skaffold/initializer/errors/errors.go b/pkg/skaffold/initializer/errors/errors.go
index d03b06456..fa48390dd 100644
--- a/pkg/skaffold/initializer/errors/errors.go
+++ b/pkg/skaffold/initializer/errors/errors.go
@@ -23,7 +23,7 @@ type NoBuilderErr struct{}
 
 func (e NoBuilderErr) ExitCode() int { return 101 }
 func (e NoBuilderErr) Error() string {
-	return "one or more valid builder configuration (Dockerfile or Jib configuration) must be present to build images with skaffold; please provide at least one build config and try again or run `skaffold init --skip-build`"
+	return "one or more valid build configuration must be present to build images with Skaffold; please provide at least one build config and try again, or run `skaffold init --skip-build`"
 }
 
 // NoManifestErr is an error returned by `skaffold init` when no valid Kubernetes manifest is found.
@@ -34,7 +34,7 @@ func (e NoManifestErr) Error() string {
 	return "one or more valid Kubernetes manifests are required to run skaffold"
 }
 
-// PreExistingConfigErr is an error returned by `skaffold init` when a skaffold config file already exists.
+// PreExistingConfigErr is an error returned by `skaffold init` when a Skaffold config file already exists.
 type PreExistingConfigErr struct {
 	Path string
 }
@@ -57,5 +57,5 @@ type NoHelmChartsErr struct{}
 
 func (e NoHelmChartsErr) ExitCode() int { return 105 }
 func (e NoHelmChartsErr) Error() string {
-	return "one or more valid helm charts or kubernetes manifests are required to run skaffold"
+	return "one or more valid Helm charts or Kubernetes manifests are required to run Skaffold"
 }
diff --git a/pkg/skaffold/runner/runner.go b/pkg/skaffold/runner/runner.go
index e37ddba28..56053140f 100644
--- a/pkg/skaffold/runner/runner.go
+++ b/pkg/skaffold/runner/runner.go
@@ -35,7 +35,7 @@ type Runner interface {
 	Apply(context.Context, io.Writer) error
 	ApplyDefaultRepo(tag string) (string, error)
 	Build(context.Context, io.Writer, []*latest.Artifact) ([]graph.Artifact, error)
-	Cleanup(context.Context, io.Writer, bool, manifest.ManifestList) error
+	Cleanup(context.Context, io.Writer, bool, *manifest.ManifestListByConfig) error
 	Dev(context.Context, io.Writer, []*latest.Artifact) error
 	// Deploy and DeployAndLog: Do they need the `graph.Artifact` and could use render output.
 	Deploy(context.Context, io.Writer, []graph.Artifact, *manifest.ManifestListByConfig) error
diff --git a/pkg/skaffold/runner/timings.go b/pkg/skaffold/runner/timings.go
index f2a660f1b..6d2be1104 100644
--- a/pkg/skaffold/runner/timings.go
+++ b/pkg/skaffold/runner/timings.go
@@ -107,11 +107,11 @@ func (w withTimings) Deploy(ctx context.Context, out io.Writer, builds []graph.A
 	return err
 }
 
-func (w withTimings) Cleanup(ctx context.Context, out io.Writer, dryRun bool, list manifest.ManifestList) error {
+func (w withTimings) Cleanup(ctx context.Context, out io.Writer, dryRun bool, config *manifest.ManifestListByConfig) error {
 	start := time.Now()
 	output.Default.Fprintln(out, "Cleaning up...")
 
-	err := w.Deployer.Cleanup(ctx, out, dryRun, list)
+	err := w.Deployer.Cleanup(ctx, out, dryRun, config)
 	if err != nil {
 		return err
 	}
diff --git a/pkg/skaffold/runner/v2/cleanup.go b/pkg/skaffold/runner/v2/cleanup.go
index 729215738..1fb93e7ec 100644
--- a/pkg/skaffold/runner/v2/cleanup.go
+++ b/pkg/skaffold/runner/v2/cleanup.go
@@ -22,6 +22,6 @@ import (
 	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/kubernetes/manifest"
 )
 
-func (r *SkaffoldRunner) Cleanup(ctx context.Context, out io.Writer, dryRun bool, list manifest.ManifestList) error {
-	return r.deployer.Cleanup(ctx, out, dryRun, list)
+func (r *SkaffoldRunner) Cleanup(ctx context.Context, out io.Writer, dryRun bool, manifestListByConfig *manifest.ManifestListByConfig) error {
+	return r.deployer.Cleanup(ctx, out, dryRun, manifestListByConfig)
 }


Test patch:
diff --git a/cmd/skaffold/app/cmd/dev_test.go b/cmd/skaffold/app/cmd/dev_test.go
index 6ab37b8ca..ff5d3347b 100644
--- a/cmd/skaffold/app/cmd/dev_test.go
+++ b/cmd/skaffold/app/cmd/dev_test.go
@@ -22,6 +22,7 @@ import (
 	"testing"
 
 	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/config"
+	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/graph"
 	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/kubernetes/manifest"
 	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/runner"
 	runcontext "github.com/GoogleContainerTools/skaffold/pkg/skaffold/runner/runcontext/v2"
@@ -32,10 +33,11 @@ import (
 
 type mockDevRunner struct {
 	runner.Runner
-	hasBuilt    bool
-	hasDeployed bool
-	errDev      error
-	calls       []string
+	hasBuilt          bool
+	hasDeployed       bool
+	errDev            error
+	calls             []string
+	manifestsByConfig *manifest.ManifestListByConfig
 }
 
 func (r *mockDevRunner) Dev(context.Context, io.Writer, []*latest.Artifact) error {
@@ -58,11 +60,16 @@ func (r *mockDevRunner) Prune(context.Context, io.Writer) error {
 	return nil
 }
 
-func (r *mockDevRunner) Cleanup(context.Context, io.Writer, bool, manifest.ManifestList) error {
+func (r *mockDevRunner) Cleanup(context.Context, io.Writer, bool, *manifest.ManifestListByConfig) error {
 	r.calls = append(r.calls, "Cleanup")
 	return nil
 }
 
+func (r *mockDevRunner) Render(ctx context.Context, out io.Writer, builds []graph.Artifact, offline bool) (*manifest.ManifestListByConfig, error) {
+	r.calls = append(r.calls, "Render")
+	return r.manifestsByConfig, nil
+}
+
 func TestDoDev(t *testing.T) {
 	tests := []struct {
 		description   string
@@ -74,7 +81,7 @@ func TestDoDev(t *testing.T) {
 			description:   "cleanup and then prune",
 			hasBuilt:      true,
 			hasDeployed:   true,
-			expectedCalls: []string{"Dev", "HasDeployed", "HasBuilt", "Cleanup", "Prune"},
+			expectedCalls: []string{"Dev", "HasDeployed", "HasBuilt", "Render", "Cleanup", "Prune"},
 		},
 		{
 			description:   "hasn't deployed",
@@ -139,7 +146,11 @@ func (m *mockConfigChangeRunner) Prune(context.Context, io.Writer) error {
 	return nil
 }
 
-func (m *mockConfigChangeRunner) Cleanup(context.Context, io.Writer, bool, manifest.ManifestList) error {
+func (m *mockConfigChangeRunner) Render(ctx context.Context, out io.Writer, builds []graph.Artifact, offline bool) (*manifest.ManifestListByConfig, error) {
+	return nil, nil
+}
+
+func (m *mockConfigChangeRunner) Cleanup(context.Context, io.Writer, bool, *manifest.ManifestListByConfig) error {
 	return nil
 }
 
diff --git a/cmd/skaffold/app/cmd/init_test.go b/cmd/skaffold/app/cmd/init_test.go
index abee6f584..b20b1675c 100644
--- a/cmd/skaffold/app/cmd/init_test.go
+++ b/cmd/skaffold/app/cmd/init_test.go
@@ -54,6 +54,7 @@ func TestFlagsToConfigVersion(t *testing.T) {
 				Analyze:                false,
 				EnableJibInit:          true,
 				EnableJibGradleInit:    false,
+				EnableKoInit:           false,
 				EnableBuildpacksInit:   true,
 				EnableNewInitFormat:    true,
 				BuildpacksBuilder:      "gcr.io/buildpacks/builder:v1",
@@ -77,6 +78,7 @@ func TestFlagsToConfigVersion(t *testing.T) {
 				"--analyze",
 				"--XXenableJibInit",
 				"--XXenableJibGradleInit",
+				"--XXenableKoInit=true",
 				"--XXenableBuildpacksInit",
 				"--XXenableNewInitFormat",
 				"--XXdefaultBuildpacksBuilder", "buildpacks/builder",
@@ -91,6 +93,7 @@ func TestFlagsToConfigVersion(t *testing.T) {
 				Analyze:                true,
 				EnableJibInit:          true,
 				EnableJibGradleInit:    true,
+				EnableKoInit:           true,
 				EnableBuildpacksInit:   true,
 				EnableNewInitFormat:    true,
 				BuildpacksBuilder:      "buildpacks/builder",
@@ -104,6 +107,8 @@ func TestFlagsToConfigVersion(t *testing.T) {
 			args: []string{
 				"init",
 				"--XXenableJibInit",
+				"--XXenableKoInit=false",
+				"--XXenableBuildpacksInit=false",
 			},
 			expectedConfig: config.Config{
 				ComposeFile:            "",
@@ -114,7 +119,33 @@ func TestFlagsToConfigVersion(t *testing.T) {
 				Force:                  false,
 				Analyze:                false,
 				EnableJibInit:          true,
-				EnableBuildpacksInit:   true,
+				EnableKoInit:           false,
+				EnableBuildpacksInit:   false,
+				EnableNewInitFormat:    true,
+				BuildpacksBuilder:      "gcr.io/buildpacks/builder:v1",
+				Opts:                   opts,
+				MaxFileSize:            maxFileSize,
+			},
+		},
+		{
+			name: "enableKoInit implies enableNewInitFormat",
+			args: []string{
+				"init",
+				"--XXenableJibInit=false",
+				"--XXenableKoInit",
+				"--XXenableBuildpacksInit=false",
+			},
+			expectedConfig: config.Config{
+				ComposeFile:            "",
+				CliArtifacts:           []string{},
+				CliKubernetesManifests: []string{},
+				SkipBuild:              false,
+				SkipDeploy:             false,
+				Force:                  false,
+				Analyze:                false,
+				EnableJibInit:          false,
+				EnableKoInit:           true,
+				EnableBuildpacksInit:   false,
 				EnableNewInitFormat:    true,
 				BuildpacksBuilder:      "gcr.io/buildpacks/builder:v1",
 				Opts:                   opts,
@@ -125,6 +156,8 @@ func TestFlagsToConfigVersion(t *testing.T) {
 			name: "enableBuildpackInit implies enableNewInitFormat",
 			args: []string{
 				"init",
+				"--XXenableJibInit=false",
+				"--XXenableKoInit=false",
 				"--XXenableBuildpacksInit",
 			},
 			expectedConfig: config.Config{
@@ -135,7 +168,8 @@ func TestFlagsToConfigVersion(t *testing.T) {
 				SkipDeploy:             false,
 				Force:                  false,
 				Analyze:                false,
-				EnableJibInit:          true,
+				EnableJibInit:          false,
+				EnableKoInit:           false,
 				EnableBuildpacksInit:   true,
 				EnableNewInitFormat:    true,
 				BuildpacksBuilder:      "gcr.io/buildpacks/builder:v1",
diff --git a/examples/custom-tests/Dockerfile b/examples/custom-tests/Dockerfile
index 415a8456f..67f8c3cbb 100644
--- a/examples/custom-tests/Dockerfile
+++ b/examples/custom-tests/Dockerfile
@@ -1,8 +1,10 @@
-FROM golang:1.15 as builder
+FROM golang:1.18 as builder
+WORKDIR /code
 COPY main.go .
+COPY go.mod .
 # `skaffold debug` sets SKAFFOLD_GO_GCFLAGS to disable compiler optimizations
 ARG SKAFFOLD_GO_GCFLAGS
-RUN go build -gcflags="${SKAFFOLD_GO_GCFLAGS}" -o /app main.go
+RUN go build -gcflags="${SKAFFOLD_GO_GCFLAGS}" -trimpath -o /app main.go
 
 FROM alpine:3
 # Define GOTRACEBACK to mark this container as using the Go language runtime
diff --git a/examples/custom-tests/go.mod b/examples/custom-tests/go.mod
new file mode 100644
index 000000000..403412976
--- /dev/null
+++ b/examples/custom-tests/go.mod
@@ -0,0 +1,3 @@
+module github.com/GoogleContainerTools/skaffold/examples/custom-tests
+
+go 1.18
diff --git a/examples/structure-tests/Dockerfile b/examples/structure-tests/Dockerfile
index 415a8456f..67f8c3cbb 100644
--- a/examples/structure-tests/Dockerfile
+++ b/examples/structure-tests/Dockerfile
@@ -1,8 +1,10 @@
-FROM golang:1.15 as builder
+FROM golang:1.18 as builder
+WORKDIR /code
 COPY main.go .
+COPY go.mod .
 # `skaffold debug` sets SKAFFOLD_GO_GCFLAGS to disable compiler optimizations
 ARG SKAFFOLD_GO_GCFLAGS
-RUN go build -gcflags="${SKAFFOLD_GO_GCFLAGS}" -o /app main.go
+RUN go build -gcflags="${SKAFFOLD_GO_GCFLAGS}" -trimpath -o /app main.go
 
 FROM alpine:3
 # Define GOTRACEBACK to mark this container as using the Go language runtime
diff --git a/examples/structure-tests/go.mod b/examples/structure-tests/go.mod
new file mode 100644
index 000000000..3348044c6
--- /dev/null
+++ b/examples/structure-tests/go.mod
@@ -0,0 +1,3 @@
+module github.com/GoogleContainerTools/skaffold/examples/structure-tests
+
+go 1.18
diff --git a/integration/delete_test.go b/integration/delete_test.go
new file mode 100644
index 000000000..e37004b7b
--- /dev/null
+++ b/integration/delete_test.go
@@ -0,0 +1,73 @@
+/*
+Copyright 2022 The Skaffold Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package integration
+
+import (
+	"testing"
+	"time"
+
+	"github.com/GoogleContainerTools/skaffold/integration/skaffold"
+)
+
+func TestDelete(t *testing.T) {
+	var tests = []struct {
+		description string
+		dir         string
+		args        []string
+		pods        []string
+		deployments []string
+		env         []string
+	}{
+		{
+			description: "getting-started",
+			dir:         "testdata/getting-started",
+			pods:        []string{"getting-started"},
+		},
+		{
+			description: "microservices",
+			dir:         "examples/microservices",
+			args:        []string{"--status-check=false"},
+			deployments: []string{"leeroy-app", "leeroy-web"},
+		},
+		{
+			description: "multi-config-microservices",
+			dir:         "examples/multi-config-microservices",
+			deployments: []string{"leeroy-app", "leeroy-web"},
+		},
+		{
+			description: "multiple deployers",
+			dir:         "testdata/deploy-multiple",
+			pods:        []string{"deploy-kubectl", "deploy-kustomize"},
+		},
+	}
+
+	MarkIntegrationTest(t, CanRunWithoutGcp)
+
+	for _, test := range tests {
+		t.Run(test.description, func(t *testing.T) {
+			ns, client := SetupNamespace(t)
+
+			args := append(test.args, "--cache-artifacts=false")
+			skaffold.Run(args...).InDir(test.dir).InNs(ns.Name).WithEnv(test.env).RunOrFail(t)
+
+			client.WaitForPodsReady(test.pods...)
+			client.waitForDeploymentsToStabilizeWithTimeout(time.Minute*2, test.deployments...)
+
+			skaffold.Delete().InDir(test.dir).InNs(ns.Name).WithEnv(test.env).RunOrFail(t)
+		})
+	}
+}
diff --git a/pkg/skaffold/build/ko/init/init_test.go b/pkg/skaffold/build/ko/init/init_test.go
new file mode 100644
index 000000000..1138db4af
--- /dev/null
+++ b/pkg/skaffold/build/ko/init/init_test.go
@@ -0,0 +1,52 @@
+/*
+Copyright 2022 The Skaffold Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package init
+
+import (
+	"testing"
+
+	"github.com/GoogleContainerTools/skaffold/testutil"
+)
+
+func TestValidate(t *testing.T) {
+	tests := []struct {
+		description string
+		path        string
+		expect      bool
+	}{
+		{
+			description: "go.mod in current directory",
+			path:        "go.mod",
+			expect:      true,
+		},
+		{
+			description: "go.mod in subdirectory",
+			path:        "foo/go.mod",
+			expect:      true,
+		},
+		{
+			description: "not go.mod",
+			path:        "Gopkg.toml",
+			expect:      false,
+		},
+	}
+	for _, test := range tests {
+		testutil.Run(t, test.description, func(t *testutil.T) {
+			t.CheckDeepEqual(test.expect, Validate(test.path))
+		})
+	}
+}
diff --git a/pkg/skaffold/deploy/cloudrun/accessor_test.go b/pkg/skaffold/deploy/cloudrun/accessor_test.go
index 34e2a82ec..cf39ea936 100644
--- a/pkg/skaffold/deploy/cloudrun/accessor_test.go
+++ b/pkg/skaffold/deploy/cloudrun/accessor_test.go
@@ -154,6 +154,32 @@ func TestResourcesAddedConfigurePorts(t *testing.T) {
 				},
 			},
 		},
+		{
+			name: "resources added twice only have one port forward configured",
+			resources: []RunResourceName{
+				{
+					Project: "test-proj",
+					Region:  "test-region",
+					Service: "test-service",
+				},
+				{
+					Project: "test-proj",
+					Region:  "test-region",
+					Service: "test-service",
+				},
+			},
+			forwardConfigs: []*latest.PortForwardResource{},
+			outputs: []forwardedResource{
+				{
+					name: RunResourceName{
+						Project: "test-proj",
+						Region:  "test-region",
+						Service: "test-service",
+					},
+					port: 0,
+				},
+			},
+		},
 	}
 
 	for _, test := range tests {
@@ -167,8 +193,11 @@ func TestResourcesAddedConfigurePorts(t *testing.T) {
 			if len(test.outputs) != len(accessor.resources.resources) {
 				t.Fatalf("Mismatch in expected outputs. Expected %v, got %v", test.outputs, accessor.resources.resources)
 			}
-			for i, output := range test.outputs {
-				got := accessor.resources.resources[i]
+			for _, output := range test.outputs {
+				got, found := accessor.resources.resources[output.name]
+				if !found {
+					t.Fatalf("expected to find port forward for %v but got nothing", output.name)
+				}
 				if output.name != got.name || output.port != got.port {
 					t.Fatalf("did not get expected port set. Expected %v, got %v", output, got)
 				}
diff --git a/pkg/skaffold/deploy/cloudrun/deploy_test.go b/pkg/skaffold/deploy/cloudrun/deploy_test.go
index c0b55254c..382ba5292 100644
--- a/pkg/skaffold/deploy/cloudrun/deploy_test.go
+++ b/pkg/skaffold/deploy/cloudrun/deploy_test.go
@@ -327,9 +327,11 @@ func TestCleanup(tOuter *testing.T) {
 			deployer, _ := NewDeployer(&runcontext.RunContext{}, &label.DefaultLabeller{}, &latest.CloudRunDeploy{ProjectID: test.defaultProject, Region: test.region}, configName)
 			deployer.clientOptions = append(deployer.clientOptions, option.WithEndpoint(ts.URL), option.WithoutAuthentication())
 			deployer.useGcpOptions = false
+			manifestListByConfig := manifest.NewManifestListByConfig()
 			manifest, _ := json.Marshal(test.toDelete)
 			manifests := [][]byte{manifest}
-			err := deployer.Cleanup(context.Background(), os.Stderr, false, manifests)
+			manifestListByConfig.Add(configName, manifests)
+			err := deployer.Cleanup(context.Background(), os.Stderr, false, &manifestListByConfig)
 			if test.httpErr == 0 && err != nil {
 				t.Fatalf("Expected success but got err: %v", err)
 			} else if test.httpErr != 0 && err == nil {
diff --git a/pkg/skaffold/deploy/deploy_mux_test.go b/pkg/skaffold/deploy/deploy_mux_test.go
index f6a4212f5..9f9fc6796 100644
--- a/pkg/skaffold/deploy/deploy_mux_test.go
+++ b/pkg/skaffold/deploy/deploy_mux_test.go
@@ -87,7 +87,7 @@ func (m *MockDeployer) Dependencies() ([]string, error) {
 	return m.dependencies, m.dependenciesErr
 }
 
-func (m *MockDeployer) Cleanup(context.Context, io.Writer, bool, manifest.ManifestList) error {
+func (m *MockDeployer) Cleanup(context.Context, io.Writer, bool, *manifest.ManifestListByConfig) error {
 	return m.cleanupErr
 }
 
diff --git a/pkg/skaffold/deploy/kubectl/kubectl_test.go b/pkg/skaffold/deploy/kubectl/kubectl_test.go
index b8a935441..a9edb4d47 100644
--- a/pkg/skaffold/deploy/kubectl/kubectl_test.go
+++ b/pkg/skaffold/deploy/kubectl/kubectl_test.go
@@ -337,7 +337,7 @@ func TestKubectlCleanup(t *testing.T) {
 				true)
 			t.CheckNoError(errR)
 
-			err = k.Cleanup(context.Background(), io.Discard, test.dryRun, m.GetForConfig(configName))
+			err = k.Cleanup(context.Background(), io.Discard, test.dryRun, m)
 
 			t.CheckError(test.shouldErr, err)
 		})
diff --git a/pkg/skaffold/render/generate/generate_test.go b/pkg/skaffold/render/generate/generate_test.go
index f15e39b2a..d638b6e5d 100644
--- a/pkg/skaffold/render/generate/generate_test.go
+++ b/pkg/skaffold/render/generate/generate_test.go
@@ -183,7 +183,7 @@ func TestGenerate(t *testing.T) {
 
 func TestGenerateFromURLManifest(t *testing.T) {
 	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-		fmt.Fprintln(w, podYaml)
+		fmt.Fprint(w, podYaml)
 	}))
 
 	defer ts.Close()
diff --git a/pkg/skaffold/runner/timings_test.go b/pkg/skaffold/runner/timings_test.go
index 7553022e1..43ad2e134 100644
--- a/pkg/skaffold/runner/timings_test.go
+++ b/pkg/skaffold/runner/timings_test.go
@@ -100,7 +100,7 @@ func (m *mockDeployer) Deploy(context.Context, io.Writer, []graph.Artifact, *man
 	return nil
 }
 
-func (m *mockDeployer) Cleanup(context.Context, io.Writer, bool, manifest.ManifestList) error {
+func (m *mockDeployer) Cleanup(context.Context, io.Writer, bool, *manifest.ManifestListByConfig) error {
 	if m.err {
 		return errors.New("Unable to cleanup")
 	}
diff --git a/pkg/skaffold/runner/v2/runner_test.go b/pkg/skaffold/runner/v2/runner_test.go
index 5c16cc9a4..07feabdce 100644
--- a/pkg/skaffold/runner/v2/runner_test.go
+++ b/pkg/skaffold/runner/v2/runner_test.go
@@ -143,7 +143,7 @@ func (t *TestBench) TestDependencies(context.Context, *latest.Artifact) ([]strin
 	return nil, nil
 }
 func (t *TestBench) Dependencies() ([]string, error) { return nil, nil }
-func (t *TestBench) Cleanup(ctx context.Context, out io.Writer, dryRun bool, list manifest.ManifestList) error {
+func (t *TestBench) Cleanup(ctx context.Context, out io.Writer, dryRun bool, byConfig *manifest.ManifestListByConfig) error {
 	return nil
 }
 func (t *TestBench) Prune(ctx context.Context, out io.Writer) error { return nil }