Repository: GoogleContainerTools/skaffold
Pull request #8647 (2023-04-06 03:28:29)

feat: support set value file for render
<!-- Thank you for your contribution! -->

<!-- Include if applicable: -->
Fixes: #8594 <!-- tracking issues that this PR will close -->

**Description**
 - add ``--set-value-flag`` to ``skaffold render`` command for users to provide a .env format key-value pair file to set their manifests templated fields. 
 - move ``ParseEnvVariablesFromFile`` method from verify util to skaffold pkg util as this can be a utility func across skaffold project. 


**Test Plan**
 - integration tests.



codecov[bot]: ## [Codecov](https://codecov.io/gh/GoogleContainerTools/skaffold/pull/8647?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools) Report
> Merging [#8647](https://codecov.io/gh/GoogleContainerTools/skaffold/pull/8647?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools) (c20f5a7) 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 `6.33%`.
> The diff coverage is `54.47%`.

```diff
@@            Coverage Diff             @@
##             main    #8647      +/-   ##
==========================================
- Coverage   70.48%   64.15%   -6.33%     
==========================================
  Files         515      615     +100     
  Lines       23150    30802    +7652     
==========================================
+ Hits        16317    19762    +3445     
- Misses       5776     9558    +3782     
- Partials     1057     1482     +425     
```


| [Impacted Files](https://codecov.io/gh/GoogleContainerTools/skaffold/pull/8647?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/completion.go](https://codecov.io/gh/GoogleContainerTools/skaffold/pull/8647?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools#diff-Y21kL3NrYWZmb2xkL2FwcC9jbWQvY29tcGxldGlvbi5nbw==) | `13.04% <0.00%> (-1.25%)` | :arrow_down: |
| [cmd/skaffold/app/cmd/config/list.go](https://codecov.io/gh/GoogleContainerTools/skaffold/pull/8647?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools#diff-Y21kL3NrYWZmb2xkL2FwcC9jbWQvY29uZmlnL2xpc3QuZ28=) | `65.21% <ø> (ø)` | |
| [cmd/skaffold/app/cmd/config/set.go](https://codecov.io/gh/GoogleContainerTools/skaffold/pull/8647?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools#diff-Y21kL3NrYWZmb2xkL2FwcC9jbWQvY29uZmlnL3NldC5nbw==) | `88.72% <ø> (ø)` | |
| [cmd/skaffold/app/cmd/config/util.go](https://codecov.io/gh/GoogleContainerTools/skaffold/pull/8647?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools#diff-Y21kL3NrYWZmb2xkL2FwcC9jbWQvY29uZmlnL3V0aWwuZ28=) | `54.28% <ø> (ø)` | |
| [cmd/skaffold/app/cmd/credits.go](https://codecov.io/gh/GoogleContainerTools/skaffold/pull/8647?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools#diff-Y21kL3NrYWZmb2xkL2FwcC9jbWQvY3JlZGl0cy5nbw==) | `100.00% <ø> (ø)` | |
| [cmd/skaffold/app/cmd/credits/export.go](https://codecov.io/gh/GoogleContainerTools/skaffold/pull/8647?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/8647?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/generate\_pipeline.go](https://codecov.io/gh/GoogleContainerTools/skaffold/pull/8647?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools#diff-Y21kL3NrYWZmb2xkL2FwcC9jbWQvZ2VuZXJhdGVfcGlwZWxpbmUuZ28=) | `60.00% <ø> (ø)` | |
| [cmd/skaffold/app/cmd/inspect\_modules.go](https://codecov.io/gh/GoogleContainerTools/skaffold/pull/8647?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools#diff-Y21kL3NrYWZmb2xkL2FwcC9jbWQvaW5zcGVjdF9tb2R1bGVzLmdv) | `65.00% <ø> (ø)` | |
| [cmd/skaffold/app/cmd/inspect\_profiles.go](https://codecov.io/gh/GoogleContainerTools/skaffold/pull/8647?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools#diff-Y21kL3NrYWZmb2xkL2FwcC9jbWQvaW5zcGVjdF9wcm9maWxlcy5nbw==) | `66.66% <ø> (ø)` | |
| ... and [39 more](https://codecov.io/gh/GoogleContainerTools/skaffold/pull/8647?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools) | |

... and [395 files with indirect coverage changes](https://codecov.io/gh/GoogleContainerTools/skaffold/pull/8647/indirect-changes?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools)

:mega: We’re building smart automated test selection to slash your CI/CD build times. [Learn more](https://about.codecov.io/iterative-testing/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools)


Patch:
diff --git a/.github/workflows/integration-linux.yml b/.github/workflows/integration-linux.yml
index 57c51eee3..e5536bac1 100644
--- a/.github/workflows/integration-linux.yml
+++ b/.github/workflows/integration-linux.yml
@@ -33,7 +33,7 @@ jobs:
     - name: Set up Go
       uses: actions/setup-go@v4
       with:
-        go-version: 1.19.*
+        go-version: 1.20.*
       id: go
 
     # Retrieve build locations with `go env`
diff --git a/.github/workflows/linters-checks.yml b/.github/workflows/linters-checks.yml
index 74b970b3e..85aac0b68 100644
--- a/.github/workflows/linters-checks.yml
+++ b/.github/workflows/linters-checks.yml
@@ -17,7 +17,7 @@ jobs:
       - name: Set up Go
         uses: actions/setup-go@v4
         with:
-          go-version: 1.19.*
+          go-version: 1.20.*
         id: go
 
       - name: Check out code into the Go module directory
diff --git a/.github/workflows/performance-comparison.yml b/.github/workflows/performance-comparison.yml
index 544ccbd81..6eed3399b 100644
--- a/.github/workflows/performance-comparison.yml
+++ b/.github/workflows/performance-comparison.yml
@@ -41,7 +41,7 @@ jobs:
     - name: Set up Go
       uses: actions/setup-go@v4
       with:
-        go-version: 1.19.*
+        go-version: 1.20.*
       id: go
 
     - name: Check out code into the Go module directory
diff --git a/.github/workflows/verify-examples.yml b/.github/workflows/verify-examples.yml
index 5accc951e..5f913cef0 100644
--- a/.github/workflows/verify-examples.yml
+++ b/.github/workflows/verify-examples.yml
@@ -23,7 +23,7 @@ jobs:
     - name: Set up Go
       uses: actions/setup-go@v4
       with:
-        go-version: 1.19.*
+        go-version: 1.20.*
       id: go
 
     # Skip changes not affecting examples or integration/examples
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 167b2787b..dc72121e5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,58 @@
+# v2.0.8 Release - 4/17/2023
+**Linux amd64**
+`curl -Lo skaffold https://storage.googleapis.com/skaffold/releases/v2.0.8/skaffold-linux-amd64 && chmod +x skaffold && sudo mv skaffold /usr/local/bin`
+
+**Linux arm64**
+`curl -Lo skaffold https://storage.googleapis.com/skaffold/releases/v2.0.8/skaffold-linux-arm64 && chmod +x skaffold && sudo mv skaffold /usr/local/bin`
+
+**macOS amd64**
+`curl -Lo skaffold https://storage.googleapis.com/skaffold/releases/v2.0.8/skaffold-darwin-amd64 && chmod +x skaffold && sudo mv skaffold /usr/local/bin`
+
+**macOS arm64**
+`curl -Lo skaffold https://storage.googleapis.com/skaffold/releases/v2.0.8/skaffold-darwin-arm64 && chmod +x skaffold && sudo mv skaffold /usr/local/bin`
+
+**Windows**
+https://storage.googleapis.com/skaffold/releases/v2.0.8/skaffold-windows-amd64.exe
+
+**Docker image**
+`gcr.io/k8s-skaffold/skaffold:v2.0.8`
+
+Fixes/Chores:
+* chore: chore: bump helm to v3.10.2 [#8684](https://github.com/GoogleContainerTools/skaffold/pull/8684)
+
+# v2.3.1 Release - 4/11/2023
+**Linux amd64**
+`curl -Lo skaffold https://storage.googleapis.com/skaffold/releases/v2.3.1/skaffold-linux-amd64 && chmod +x skaffold && sudo mv skaffold /usr/local/bin`
+
+**Linux arm64**
+`curl -Lo skaffold https://storage.googleapis.com/skaffold/releases/v2.3.1/skaffold-linux-arm64 && chmod +x skaffold && sudo mv skaffold /usr/local/bin`
+
+**macOS amd64**
+`curl -Lo skaffold https://storage.googleapis.com/skaffold/releases/v2.3.1/skaffold-darwin-amd64 && chmod +x skaffold && sudo mv skaffold /usr/local/bin`
+
+**macOS arm64**
+`curl -Lo skaffold https://storage.googleapis.com/skaffold/releases/v2.3.1/skaffold-darwin-arm64 && chmod +x skaffold && sudo mv skaffold /usr/local/bin`
+
+**Windows**
+https://storage.googleapis.com/skaffold/releases/v2.3.1/skaffold-windows-amd64.exe
+
+**Docker image**
+`gcr.io/k8s-skaffold/skaffold:v2.3.1`
+
+Fixes:
+* fix: update skaffold verify to respect deploy default namespace field (#8660)
+* fix: fix issue where verify would panic if a jobManifestPath with no spec.template.spec.metadata.labels existed (#8618)
+* fix: deploy to multiple namespaces (#8623)
+
+Features:
+* feat: better Job support by allowing skaffold to re-apply Jobs by removing child pod label transform (#8659)
+* feat: add support for deployment cancellation and error surface when admission webhook blocks pod skaffold is waiting on (#8624)
+* feat: add new 'skaffold inspect executionModes' command (#8651)
+
+Chores:
+* chore: upgrade docker and make integration-in-docker to use docker dependencies from pr (#8596)
+* chore: bump image deps (#8612)
+
 # v2.3.0 Release - 03/28/2023
 **Linux amd64**
 `curl -Lo skaffold https://storage.googleapis.com/skaffold/releases/v2.3.0/skaffold-linux-amd64 && chmod +x skaffold && sudo mv skaffold /usr/local/bin`
diff --git a/Makefile b/Makefile
index 514d4b5a9..e0529afa0 100644
--- a/Makefile
+++ b/Makefile
@@ -41,7 +41,6 @@ SKAFFOLD_TEST_PACKAGES = ./pkg/skaffold/... ./cmd/... ./hack/... ./pkg/webhook/.
 GO_FILES = $(shell find . -type f -name '*.go' -not -path "./pkg/diag/*")
 
 VERSION_PACKAGE = $(REPOPATH)/v2/pkg/skaffold/version
-FIRELOG_PACKAGE = $(REPOPATH)/v2/pkg/skaffold/instrumentation/firelog
 COMMIT = $(shell git rev-parse HEAD)
 
 ifeq "$(strip $(VERSION))" ""
@@ -58,9 +57,6 @@ endif
 GO_LDFLAGS = -X $(VERSION_PACKAGE).version=$(VERSION)
 GO_LDFLAGS += -X $(VERSION_PACKAGE).buildDate=$(BUILD_DATE)
 GO_LDFLAGS += -X $(VERSION_PACKAGE).gitCommit=$(COMMIT)
-ifdef FIRELOG_API_KEY
-    GO_LDFLAGS += -X $(VERSION_PACKAGE).APIKey=$(FIRELOG_API_KEY)
-endif
 GO_LDFLAGS += -s -w
 
 GO_BUILD_TAGS = timetzdata
diff --git a/cmd/skaffold/app/cmd/flags.go b/cmd/skaffold/app/cmd/flags.go
index 53cdde1db..cec6992c7 100644
--- a/cmd/skaffold/app/cmd/flags.go
+++ b/cmd/skaffold/app/cmd/flags.go
@@ -675,7 +675,7 @@ The build result from a previous 'skaffold build --file-output' run can be used
 	{
 		Name:          "docker-network",
 		Shorthand:     "",
-		Usage:         "Run verify tests in the specified docker network",
+		Usage:         "Name of an existing docker network to use when running the verify tests. If not specified, Skaffold will create a new network to use of the form 'skaffold-network-<uuid>'",
 		Value:         &opts.VerifyDockerNetwork,
 		DefValue:      "",
 		FlagAddMethod: "StringVar",
@@ -754,7 +754,8 @@ The build result from a previous 'skaffold build --file-output' run can be used
 		DefValue:      false,
 		FlagAddMethod: "BoolVar",
 		DefinedOn:     []string{"dev", "debug"},
-	}, {
+	},
+	{
 		Name:          "set",
 		Usage:         "overrides templated manifest fields by provided key-value pairs",
 		Value:         &opts.ManifestsOverrides,
@@ -762,6 +763,14 @@ The build result from a previous 'skaffold build --file-output' run can be used
 		FlagAddMethod: "StringSliceVar",
 		DefinedOn:     []string{"render"},
 	},
+	{
+		Name:          "set-value-file",
+		Usage:         "overrides templated manifest fields by a file containing key-value pairs in .env file format",
+		Value:         &opts.ManifestsValueFile,
+		DefValue:      "",
+		FlagAddMethod: "StringVar",
+		DefinedOn:     []string{"render"},
+	},
 }
 
 func methodNameByType(v reflect.Value) string {
diff --git a/cmd/skaffold/app/cmd/inspect.go b/cmd/skaffold/app/cmd/inspect.go
index 8a6de4cf6..edfb1a6b3 100644
--- a/cmd/skaffold/app/cmd/inspect.go
+++ b/cmd/skaffold/app/cmd/inspect.go
@@ -43,7 +43,8 @@ func NewCmdInspect() *cobra.Command {
 		WithDescription("Helper commands for Cloud Code IDEs to interact with and modify skaffold configuration files.").
 		WithPersistentFlagAdder(cmdInspectFlags).
 		Hidden().
-		WithCommands(cmdModules(), cmdProfiles(), cmdBuildEnv(), cmdTests(), cmdNamespaces(), cmdJobManifestPaths())
+		WithCommands(cmdModules(), cmdProfiles(), cmdBuildEnv(), cmdTests(), cmdNamespaces(),
+			cmdJobManifestPaths(), cmdExecutionModes())
 }
 
 func cmdInspectFlags(f *pflag.FlagSet) {
diff --git a/cmd/skaffold/app/cmd/inspect_execution_modes.go b/cmd/skaffold/app/cmd/inspect_execution_modes.go
new file mode 100644
index 000000000..66caa15a0
--- /dev/null
+++ b/cmd/skaffold/app/cmd/inspect_execution_modes.go
@@ -0,0 +1,55 @@
+/*
+Copyright 2021 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 cmd
+
+import (
+	"context"
+	"io"
+
+	"github.com/spf13/cobra"
+
+	"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/inspect"
+	executionModes "github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/inspect/executionModes"
+)
+
+func cmdExecutionModes() *cobra.Command {
+	return NewCmd("executionModes").
+		WithDescription("View skaffold executionMode information defined in the specified skaffold configuration").
+		WithCommands(cmdExecutionModesList())
+}
+
+func cmdExecutionModesList() *cobra.Command {
+	return NewCmd("list").
+		WithExample("Get list of executionModes", "inspect executionModes list --format json").
+		WithExample("Get list of executionModes targeting a specific configuration", "inspect executionModes list --profile local --format json").
+		WithDescription("Print the list of executionModes that would be run for a given configuration (default skaffold configuration, specific module, specific profile, etc).").
+		WithArgs(func(cmd *cobra.Command, args []string) error {
+			// skaffold inspect executionModes list take in an optional list of customActions as well
+			return nil
+		}, listExecutionModes)
+}
+
+func listExecutionModes(ctx context.Context, out io.Writer, args []string) error {
+	return executionModes.PrintExecutionModesList(ctx, out, inspect.Options{
+		Filename:          inspectFlags.filename,
+		RepoCacheDir:      inspectFlags.repoCacheDir,
+		OutFormat:         inspectFlags.outFormat,
+		Modules:           inspectFlags.modules,
+		Profiles:          inspectFlags.profiles,
+		PropagateProfiles: inspectFlags.propagateProfiles,
+	}, args)
+}
diff --git a/deploy/skaffold/Dockerfile.deps b/deploy/skaffold/Dockerfile.deps
index 7a1439c20..56fe4a111 100644
--- a/deploy/skaffold/Dockerfile.deps
+++ b/deploy/skaffold/Dockerfile.deps
@@ -155,6 +155,7 @@ RUN gcloud auth configure-docker && gcloud components install --quiet \
 FROM runtime_deps
 ENV DEBIAN_FRONTEND=noninteractive
 RUN apt-get update && apt-get install --no-install-recommends --no-install-suggests -y \
+    curl \
     build-essential \
     python-setuptools \
     lsb-release \
diff --git a/deploy/skaffold/Dockerfile.deps.lts b/deploy/skaffold/Dockerfile.deps.lts
index ac86be5f9..c61c8692c 100644
--- a/deploy/skaffold/Dockerfile.deps.lts
+++ b/deploy/skaffold/Dockerfile.deps.lts
@@ -97,6 +97,7 @@ RUN gcloud auth configure-docker && gcloud components install --quiet \
 FROM runtime_deps
 ENV DEBIAN_FRONTEND=noninteractive
 RUN apt-get update && apt-get install --no-install-recommends --no-install-suggests -y \
+    curl \
     build-essential \
     python-setuptools \
     lsb-release \
diff --git a/deploy/skaffold/Dockerfile.deps.slim b/deploy/skaffold/Dockerfile.deps.slim
index 28ce6f934..fda559057 100644
--- a/deploy/skaffold/Dockerfile.deps.slim
+++ b/deploy/skaffold/Dockerfile.deps.slim
@@ -97,6 +97,7 @@ RUN gcloud auth configure-docker && gcloud components install --quiet \
 FROM runtime_deps
 ENV DEBIAN_FRONTEND=noninteractive
 RUN apt-get update && apt-get install --no-install-recommends --no-install-suggests -y \
+    curl \
     build-essential \
     python-setuptools \
     lsb-release \
diff --git a/docs-v2/content/en/docs/environment/templating.md b/docs-v2/content/en/docs/environment/templating.md
index a63bd58db..c78308d5d 100644
--- a/docs-v2/content/en/docs/environment/templating.md
+++ b/docs-v2/content/en/docs/environment/templating.md
@@ -15,14 +15,15 @@ will be `gcr.io/k8s-skaffold/example:v1`.
 
 List of fields that support templating:
 
-* `build.artifacts.[].docker.buildArgs` (see [builders]({{< relref "/docs/builders" >}}))
-* `build.artifacts.[].ko.{env,flags,labels,ldflags}` (see [`ko` builder]({{< relref "/docs/builders/builder-types/ko" >}}))
+* `build.artifacts[].docker.buildArgs` (see [builders]({{< relref "/docs/builders" >}}))
+* `build.artifacts[].ko.{env,flags,labels,ldflags}` (see [`ko` builder]({{< relref "/docs/builders/builder-types/ko" >}}))
 * `build.tagPolicy.envTemplate.template` (see [envTemplate tagger]({{< relref "/docs/taggers#envtemplate-using-values-of-environment-variables-as-tags)" >}}))
-* `deploy.helm.releases.setValueTemplates` (see [Deploying with helm]({{< relref "/docs/deployers#deploying-with-helm)" >}}))
-* `deploy.helm.releases.name` (see [Deploying with helm]({{< relref "/docs/deployers#deploying-with-helm)" >}}))
-* `deploy.helm.releases.namespace` (see [Deploying with helm]({{< relref "/docs/deployers#deploying-with-helm)" >}}))
+* `deploy.helm.releases[].chartPath` (see [Deploying with helm]({{< relref "/docs/deployers#deploying-with-helm)" >}}))
+* `deploy.helm.releases[].name` (see [Deploying with helm]({{< relref "/docs/deployers#deploying-with-helm)" >}}))
+* `deploy.helm.releases[].namespace` (see [Deploying with helm]({{< relref "/docs/deployers#deploying-with-helm)" >}}))
 * `deploy.helm.releases[].repo` (see [Deploying with helm]({{< relref "/docs/deployers#deploying-with-helm)" >}}))
-* `deploy.helm.releases.version` (see [Deploying with helm]({{< relref "/docs/deployers#deploying-with-helm)" >}}))
+* `deploy.helm.releases[].setValueTemplates` (see [Deploying with helm]({{< relref "/docs/deployers#deploying-with-helm)" >}}))
+* `deploy.helm.releases[].version` (see [Deploying with helm]({{< relref "/docs/deployers#deploying-with-helm)" >}}))
 * `deploy.kubectl.defaultNamespace`
 * `deploy.kustomize.defaultNamespace`
 * `manifests.kustomize.paths.[]`
diff --git a/docs-v2/content/en/docs/references/cli/_index.md b/docs-v2/content/en/docs/references/cli/_index.md
index 29beaf86a..8f27299bb 100644
--- a/docs-v2/content/en/docs/references/cli/_index.md
+++ b/docs-v2/content/en/docs/references/cli/_index.md
@@ -1044,6 +1044,7 @@ The build result from a previous 'skaffold build --file-output' run can be used
       --remote-cache-dir='': Specify the location of the git repositories cache (default $HOME/.skaffold/repos)
       --resource-selector-rules-file='': Path to JSON file specifying the deny list of yaml objects for skaffold to NOT transform with 'image' and 'label' field replacements.  NOTE: this list is additive to skaffold's default denylist and denylist has priority over allowlist
       --set=[]: overrides templated manifest fields by provided key-value pairs
+      --set-value-file='': overrides templated manifest fields by a file containing key-value pairs in .env file format
       --sync-remote-cache='always': Controls how Skaffold manages the remote config cache (see `remote-cache-dir`). One of `always` (default), `missing`, or `never`. `always` syncs remote repositories to latest on access. `missing` only clones remote repositories if they do not exist locally. `never` means the user takes responsibility for updating remote repositories.
   -t, --tag='': The optional custom tag to use for images which overrides the current Tagger configuration
       --wait-for-connection=false: Blocks ending execution of skaffold until the /v2/events gRPC/HTTP endpoint is hit
@@ -1079,6 +1080,7 @@ Env vars:
 * `SKAFFOLD_REMOTE_CACHE_DIR` (same as `--remote-cache-dir`)
 * `SKAFFOLD_RESOURCE_SELECTOR_RULES_FILE` (same as `--resource-selector-rules-file`)
 * `SKAFFOLD_SET` (same as `--set`)
+* `SKAFFOLD_SET_VALUE_FILE` (same as `--set-value-file`)
 * `SKAFFOLD_SYNC_REMOTE_CACHE` (same as `--sync-remote-cache`)
 * `SKAFFOLD_TAG` (same as `--tag`)
 * `SKAFFOLD_WAIT_FOR_CONNECTION` (same as `--wait-for-connection`)
@@ -1355,7 +1357,7 @@ Options:
 }
 The build result from a previous 'skaffold build --file-output' run can be used here
   -d, --default-repo='': Default repository value (overrides global config)
-      --docker-network='': Run verify tests in the specified docker network
+      --docker-network='': Name of an existing docker network to use when running the verify tests. If not specified, Skaffold will create a new network to use of the form 'skaffold-network-<uuid>'
       --env-file='': File containing env var key-value pairs that will be set in all verify container envs
   -f, --filename='skaffold.yaml': Path or URL to the Skaffold config file
   -m, --module=[]: Filter Skaffold configs to only the provided named modules
diff --git a/docs-v2/content/en/docs/renderers/_index.md b/docs-v2/content/en/docs/renderers/_index.md
index fc11b7a43..5cf62e611 100644
--- a/docs-v2/content/en/docs/renderers/_index.md
+++ b/docs-v2/content/en/docs/renderers/_index.md
@@ -7,7 +7,7 @@ aliases: [/docs/how-tos/renderers, /docs/pipeline-stages/renderers/]
 no_list: true
 ---
 
-When Skaffold renders your application to Kubernetes, it goes throught the following process:
+When Skaffold renders your application to Kubernetes, it goes through the following process:
 
 * the Skaffold renderer _renders_ the final Kubernetes manifests: Skaffold replaces untagged image names in the Kubernetes manifests with the final tagged image names.
 It also might go through the extra intermediate step of expanding templates (for helm) or calculating overlays (for kustomize).
@@ -28,4 +28,4 @@ a combination of multiple renderers in a single project.
 
 For a detailed discussion on Skaffold configuration, see
 [Skaffold Concepts]({{< relref "/docs/design/config.md" >}}) and
-[skaffold.yaml References]({{< relref "/docs/references/yaml" >}}).
\ No newline at end of file
+[skaffold.yaml References]({{< relref "/docs/references/yaml" >}}).
diff --git a/docs-v2/content/en/docs/renderers/helm.md b/docs-v2/content/en/docs/renderers/helm.md
index 8fb957818..be6813252 100644
--- a/docs-v2/content/en/docs/renderers/helm.md
+++ b/docs-v2/content/en/docs/renderers/helm.md
@@ -87,7 +87,7 @@ build:
   artifacts:
     - image: myFirstImage # must match in setValueTemplates
     - image: mySecondImage # must match in setValueTemplates
-deploy:
+manifests:
   helm:
     releases:
     - name: my-release
@@ -126,7 +126,7 @@ Example
 build:
   artifacts:
     - image: localhost/nginx  # must match in setValueTemplates w/ `/` & `-` changed to `_`
-deploy:
+manifests:
   helm:
     releases:
       - name: my-chart
@@ -155,7 +155,7 @@ build:
   artifacts:
     - image: myFirstImage  # must match in setValueTemplates
     - image: mySecondImage  # must match in setValueTemplates
-deploy:
+manifests:
   helm:
     releases:
       - name: my-chart
@@ -195,7 +195,7 @@ build:
   artifacts:
     - image: myFirstImage # must match in setValueTemplates
     - image: mySecondImage # must match in setValueTemplates
-deploy:
+manifests:
   helm:
     releases:
       - name: my-chart
@@ -242,7 +242,7 @@ build:
   artifacts:
     - image: myFirstImage # must match in setValueTemplates
     - image: mySecondImage # must match in setValueTemplates
-deploy:
+manifests:
   helm:
     releases:
       - name: my-chart
diff --git a/hack/generate-embedded-files.sh b/hack/generate-embedded-files.sh
index 7a0ee7f8f..1566ded65 100755
--- a/hack/generate-embedded-files.sh
+++ b/hack/generate-embedded-files.sh
@@ -50,4 +50,10 @@ if [[ -d ${SECRET} ]]; then
    cp -R ${SECRET} "fs/assets/secrets_generated"
 fi
 
+if [ -n "${FIRELOG_API_KEY_FILE+x}" ] && [ -f ${FIRELOG_API_KEY_FILE} ]; then
+   echo "generating embedded files for firelog API key"
+   mkdir -p "fs/assets/firelog_generated"
+   cp ${FIRELOG_API_KEY_FILE} "fs/assets/firelog_generated/key.txt"
+fi
+
 echo "Used for marking generating embedded files task complete, don't modify this." > fs/assets/check.txt
diff --git a/hack/golangci-lint.sh b/hack/golangci-lint.sh
index b810dfd46..6b5b50db5 100755
--- a/hack/golangci-lint.sh
+++ b/hack/golangci-lint.sh
@@ -18,7 +18,7 @@ set -e -o pipefail
 
 DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
 BIN=${DIR}/bin
-VERSION=1.49.0
+VERSION=1.52.2
 
 function install_linter() {
   echo "Installing GolangCI-Lint"
diff --git a/pkg/skaffold/actions/action.go b/pkg/skaffold/actions/action.go
new file mode 100644
index 000000000..99f6adc3d
--- /dev/null
+++ b/pkg/skaffold/actions/action.go
@@ -0,0 +1,64 @@
+/*
+Copyright 2023 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 actions
+
+import (
+	"context"
+	"io"
+	"time"
+)
+
+type Action struct {
+	name     string
+	timeout  time.Duration
+	tasks    []Task
+	execFunc ExecStrategy
+}
+
+func NewAction(name string, timeoutSeconds int, isFailFast bool, tasks []Task) *Action {
+	return &Action{
+		name:     name,
+		timeout:  time.Duration(timeoutSeconds * int(time.Second)),
+		execFunc: getExecFunc(isFailFast),
+		tasks:    tasks,
+	}
+}
+
+func (a Action) Name() string {
+	return a.name
+}
+
+func (a Action) Exec(ctx context.Context, out io.Writer) error {
+	ctxTimeout := ctx
+	cancel := func() {}
+
+	if a.timeout.Seconds() > 0 {
+		ctxTimeout, cancel = context.WithTimeout(ctx, a.timeout)
+	}
+
+	defer cancel()
+	return a.execFunc(ctxTimeout, out, a.tasks)
+}
+
+func (a Action) Cleanup(ctx context.Context, out io.Writer) error {
+	for _, t := range a.tasks {
+		if err := t.Cleanup(ctx, out); err != nil {
+			return err
+		}
+	}
+	return nil
+}
diff --git a/pkg/skaffold/actions/docker/exec_env.go b/pkg/skaffold/actions/docker/exec_env.go
new file mode 100644
index 000000000..a391ce631
--- /dev/null
+++ b/pkg/skaffold/actions/docker/exec_env.go
@@ -0,0 +1,224 @@
+/*
+Copyright 2023 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 docker
+
+import (
+	"context"
+	"fmt"
+	"io"
+
+	v1 "github.com/google/go-containerregistry/pkg/v1"
+	"github.com/pkg/errors"
+
+	"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/actions"
+	dockerport "github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/deploy/docker/port"
+	"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/deploy/label"
+	dockerutil "github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/docker"
+	"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/docker/logger"
+	"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/docker/tracker"
+	"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/graph"
+	"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/log"
+	"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/schema/latest"
+)
+
+type ExecEnv struct {
+	// Used to print the output from the associated tasks.
+	logger log.Logger
+
+	// Keeps track of all the containers created for each associated triggered task.
+	tracker *tracker.ContainerTracker
+
+	// Client to manage communication with the docker daemon.
+	client dockerutil.LocalDaemon
+
+	// Client used to manage the user defined port forwards.
+	portManager *dockerport.PortManager
+
+	// Configuration of user defined resources to port-forward.
+	pResources []*latest.PortForwardResource
+
+	// Network name for the docker continer.
+	network string
+
+	// Indicates if the docker network should be managed (created and deleted) by the exec environment or not.
+	shouldCreateNetwork bool
+
+	// List of all the local custom actions configurations defined, by name.
+	acsCfgByName map[string]latest.Action
+
+	// Global env variables to be injected into every container of each task.
+	envVars []string
+}
+
+var NewExecEnv = newExecEnv
+
+func newExecEnv(ctx context.Context, cfg dockerutil.Config, labeller *label.DefaultLabeller, resources []*latest.PortForwardResource, network string, envMap map[string]string, acs []latest.Action) (*ExecEnv, error) {
+	tracker := tracker.NewContainerTracker()
+	l, err := logger.NewLogger(ctx, tracker, cfg)
+	if err != nil {
+		return nil, err
+	}
+
+	client, err := dockerutil.NewAPIClient(ctx, cfg)
+	if err != nil {
+		return nil, err
+	}
+
+	shouldCreateNetwork := network == ""
+	if shouldCreateNetwork {
+		network = fmt.Sprintf("skaffold-network-%s", labeller.GetRunID())
+	}
+
+	acsCfgByName := map[string]latest.Action{}
+	for _, a := range acs {
+		acsCfgByName[a.Name] = a
+	}
+
+	envVars := []string{}
+	for name, val := range envMap {
+		envVars = append(envVars, fmt.Sprintf("%v=%v", name, val))
+	}
+
+	return &ExecEnv{
+		logger:              l,
+		client:              client,
+		tracker:             tracker,
+		portManager:         dockerport.NewPortManager(),
+		pResources:          resources,
+		network:             network,
+		shouldCreateNetwork: shouldCreateNetwork,
+		acsCfgByName:        acsCfgByName,
+		envVars:             envVars,
+	}, nil
+}
+
+func (e ExecEnv) PrepareActions(ctx context.Context, out io.Writer, allbuilds []graph.Artifact, acsNames []string) ([]actions.Action, error) {
+	if e.shouldCreateNetwork {
+		if err := e.client.NetworkCreate(ctx, e.network); err != nil {
+			return nil, fmt.Errorf("creating skaffold network %s: %w", e.network, err)
+		}
+	}
+
+	e.logger.Start(ctx, out)
+
+	return e.createActions(ctx, out, allbuilds, acsNames)
+}
+
+func (e ExecEnv) Cleanup(ctx context.Context, out io.Writer) error {
+	if !e.shouldCreateNetwork {
+		return nil
+	}
+
+	if err := e.client.NetworkRemove(ctx, e.network); err != nil {
+		return errors.Wrap(err, "cleaning up skaffold created network")
+	}
+
+	return nil
+}
+
+func (e ExecEnv) Stop() {
+	e.logger.Stop()
+}
+
+func (e ExecEnv) createActions(ctx context.Context, out io.Writer, bs []graph.Artifact, acsNames []string) ([]actions.Action, error) {
+	var trackedBuilds []graph.Artifact
+	var acs []actions.Action
+	builtArtifacts := map[string]graph.Artifact{}
+
+	for _, b := range bs {
+		builtArtifacts[b.ImageName] = b
+	}
+
+	for _, aName := range acsNames {
+		aCfg, found := e.acsCfgByName[aName]
+		if !found {
+			return nil, fmt.Errorf("action %v not found for local execution mode", aName)
+		}
+
+		ts, tracked, err := e.createTasks(ctx, out, aCfg, builtArtifacts)
+		if err != nil {
+			return nil, err
+		}
+		acs = append(acs, *actions.NewAction(aCfg.Name, *aCfg.Config.Timeout, *aCfg.Config.IsFailFast, ts))
+		trackedBuilds = append(trackedBuilds, tracked...)
+	}
+
+	e.logger.RegisterArtifacts(trackedBuilds)
+
+	return acs, nil
+}
+
+func (e ExecEnv) createTasks(ctx context.Context, out io.Writer, aCfgs latest.Action, builts map[string]graph.Artifact) ([]actions.Task, []graph.Artifact, error) {
+	var ts []actions.Task
+	var tracked []graph.Artifact
+	containerCfgs := aCfgs.Containers
+	timeout := *aCfgs.Config.Timeout
+
+	for _, cCfg := range containerCfgs {
+		art, err := e.pullArtifact(ctx, out, builts, cCfg)
+		if err != nil {
+			return nil, nil, err
+		}
+
+		ts = append(ts, NewTask(cCfg, e.client, e.portManager, e.pResources, *art, timeout, &e))
+
+		tracked = append(tracked, graph.Artifact{
+			ImageName: cCfg.Image,
+			Tag:       cCfg.Name,
+		})
+	}
+
+	return ts, tracked, nil
+}
+
+func (e ExecEnv) pullArtifact(ctx context.Context, out io.Writer, allbuilds map[string]graph.Artifact, cfg latest.VerifyContainer) (*graph.Artifact, error) {
+	ba, found := allbuilds[cfg.Image]
+	tag := cfg.Image
+	shouldPullImg := true
+
+	if found {
+		tag = ba.Tag
+		imgID, err := e.client.ImageID(ctx, tag)
+		if err != nil {
+			return nil, fmt.Errorf("getting imageID for %q: %w", tag, err)
+		}
+		shouldPullImg = imgID == ""
+	}
+
+	if shouldPullImg {
+		if err := e.client.Pull(ctx, out, tag, v1.Platform{}); err != nil {
+			return nil, err
+		}
+	}
+
+	return &graph.Artifact{
+		ImageName: cfg.Image,
+		Tag:       tag,
+	}, nil
+}
+
+func (e ExecEnv) TrackContainerFromBuild(art graph.Artifact, cID string, cName string) {
+	e.tracker.Add(art, tracker.Container{Name: cName, ID: cID})
+}
+
+func (e ExecEnv) GetContainerID(image string) (string, bool) {
+	c, found := e.tracker.ContainerForImage(image)
+	if found {
+		return c.ID, found
+	}
+	return "", false
+}
diff --git a/pkg/skaffold/actions/docker/task.go b/pkg/skaffold/actions/docker/task.go
new file mode 100644
index 000000000..de965e6b5
--- /dev/null
+++ b/pkg/skaffold/actions/docker/task.go
@@ -0,0 +1,199 @@
+/*
+Copyright 2023 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 docker
+
+import (
+	"context"
+	"fmt"
+	"io"
+	"path"
+	"strings"
+
+	"github.com/docker/docker/api/types/container"
+	"github.com/docker/go-connections/nat"
+	"github.com/google/uuid"
+	"github.com/pkg/errors"
+
+	dockerport "github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/deploy/docker/port"
+	dockerutil "github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/docker"
+	"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/graph"
+	olog "github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/output/log"
+	"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/schema/latest"
+)
+
+type Task struct {
+	// Unique task name, used as the container name.
+	name string
+
+	// Configuration to create the task container.
+	cfg latest.VerifyContainer
+
+	// Client to manage communication with the docker daemon.
+	client dockerutil.LocalDaemon
+
+	// Configuration of user defined resources to port-forward.
+	pResources []*latest.PortForwardResource
+
+	// Client used to manage the user defined port forwards.
+	portManager *dockerport.PortManager
+
+	// Artifact representing the image and container.
+	artifact graph.Artifact
+
+	// Network name for the docker continer.
+	network string
+
+	// Global env variables to be injected into the container.
+	envVars []string
+
+	// Reference to the associated execution environment.
+	execEnv *ExecEnv
+}
+
+var NewTask = newTask
+
+func newTask(c latest.VerifyContainer, client dockerutil.LocalDaemon, pM *dockerport.PortManager, resources []*latest.PortForwardResource, artifact graph.Artifact, timeout int, execEnv *ExecEnv) Task {
+	return Task{
+		name:        c.Name,
+		cfg:         c,
+		client:      client,
+		pResources:  resources,
+		portManager: pM,
+		artifact:    artifact,
+		network:     execEnv.network,
+		envVars:     execEnv.envVars,
+		execEnv:     execEnv,
+	}
+}
+
+func (t Task) Name() string {
+	return t.name
+}
+
+func (t Task) Exec(ctx context.Context, out io.Writer) error {
+	cn := t.containerName(ctx, t.cfg.Name)
+
+	opts, err := t.containerCreateOpts(ctx, cn)
+	if err != nil {
+		return err
+	}
+
+	statusCh, errCh, id, err := t.client.Run(ctx, out, *opts)
+	if err != nil {
+		return errors.Wrap(err, "creating container in local docker")
+	}
+
+	t.execEnv.TrackContainerFromBuild(graph.Artifact{
+		ImageName: t.cfg.Name,
+		Tag:       t.cfg.Name,
+	}, id, cn)
+
+	var containerErr error
+	select {
+	case containerErr = <-errCh:
+	case status := <-statusCh:
+		if status.StatusCode != 0 {
+			containerErr = errors.New(fmt.Sprintf("%q running container image %q errored during run with status code: %d", t.name, opts.ContainerConfig.Image, status.StatusCode))
+		}
+	case <-ctx.Done():
+		containerErr = ctx.Err()
+	}
+
+	return containerErr
+}
+
+func (t Task) Cleanup(ctx context.Context, out io.Writer) error {
+	id, found := t.execEnv.GetContainerID(t.cfg.Name)
+	if !found {
+		return nil
+	}
+
+	if err := t.client.Delete(ctx, out, id); err != nil {
+		return err
+	}
+	t.portManager.RelinquishPorts(id)
+
+	return nil
+}
+
+func (t Task) containerCreateOpts(ctx context.Context, containerName string) (*dockerutil.ContainerCreateOpts, error) {
+	containerCfg, err := t.generateContainerCfg(ctx)
+	if err != nil {
+		return nil, err
+	}
+
+	bindings, err := t.portManager.AllocatePorts(t.artifact.ImageName, t.pResources, containerCfg, nat.PortMap{})
+	if err != nil {
+		return nil, err
+	}
+
+	return &dockerutil.ContainerCreateOpts{
+		Name:            containerName,
+		Network:         t.network,
+		ContainerConfig: containerCfg,
+		Bindings:        bindings,
+		Wait:            true,
+	}, nil
+}
+
+func (t Task) generateContainerCfg(ctx context.Context) (*container.Config, error) {
+	containerCfg, err := t.containerConfigFromImage(ctx)
+	if err != nil {
+		return nil, err
+	}
+
+	if len(t.cfg.Command) != 0 {
+		containerCfg.Entrypoint = t.cfg.Command
+	}
+
+	if len(t.cfg.Args) != 0 {
+		containerCfg.Cmd = t.cfg.Args
+	}
+
+	envVars := []string{}
+	for _, envVar := range t.cfg.Env {
+		envVars = append(envVars, fmt.Sprintf("%v=%v", envVar.Name, envVar.Value))
+	}
+
+	containerCfg.Env = append(envVars, t.envVars...)
+
+	return containerCfg, nil
+}
+
+func (t Task) containerName(ctx context.Context, name string) string {
+	// this is done to fix the naming convention of non-skaffold built images which custom actions supports
+	name = path.Base(strings.Split(name, ":")[0])
+	currentName := name
+
+	for t.client.ContainerExists(ctx, currentName) {
+		currentName = fmt.Sprintf("%s-%s", name, uuid.New().String()[0:8])
+	}
+
+	if currentName != name {
+		olog.Entry(ctx).Debugf("container %s already present in local daemon: using %s instead", name, currentName)
+	}
+	return currentName
+}
+
+func (t Task) containerConfigFromImage(ctx context.Context) (*container.Config, error) {
+	config, _, err := t.client.ImageInspectWithRaw(ctx, t.artifact.Tag)
+	if err != nil {
+		return nil, err
+	}
+	config.Config.Image = t.artifact.Tag // the client replaces this with an image ID. put back the originally provided tagged image
+	return config.Config, err
+}
diff --git a/pkg/skaffold/actions/runner.go b/pkg/skaffold/actions/runner.go
new file mode 100644
index 000000000..f9ff89000
--- /dev/null
+++ b/pkg/skaffold/actions/runner.go
@@ -0,0 +1,157 @@
+/*
+Copyright 2023 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 actions
+
+import (
+	"context"
+	"fmt"
+	"io"
+	"math"
+
+	"github.com/fatih/semgroup"
+	"golang.org/x/sync/errgroup"
+
+	"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/graph"
+	"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/output/log"
+)
+
+type Runner struct {
+	// Map to access the associated Exec environment of a given action.
+	execEnvByAction map[string]ExecEnv
+
+	// List with all the created exec environments; this helps to execute all the actions in the same order always.
+	orderedExecEnvs []ExecEnv
+
+	// Map to access the list of associated actions of a given Execution environment.
+	acsByExecEnv map[ExecEnv][]string
+}
+
+func NewRunner(execEnvByAction map[string]ExecEnv, orderedExecEnvs []ExecEnv, acsByExecEnv map[ExecEnv][]string) Runner {
+	return Runner{execEnvByAction, orderedExecEnvs, acsByExecEnv}
+}
+
+func (r Runner) ExecAll(ctx context.Context, out io.Writer, allbuilds []graph.Artifact) error {
+	acs, err := r.prepareAllActions(ctx, out, allbuilds)
+	if err != nil {
+		return err
+	}
+
+	defer r.cleanup(ctx, out, acs, r.orderedExecEnvs)
+	return execWithFailingSafe(ctx, out, acs)
+}
+
+func (r Runner) Exec(ctx context.Context, out io.Writer, allbuilds []graph.Artifact, aName string) error {
+	execEnv, found := r.execEnvByAction[aName]
+	if !found {
+		return fmt.Errorf("custom action %v not found", aName)
+	}
+
+	acs, err := execEnv.PrepareActions(ctx, out, allbuilds, []string{aName})
+	if err != nil {
+		return err
+	}
+
+	// We expect only one action to be created.
+	if len(acs) != 1 {
+		return fmt.Errorf("failed to create %v action", aName)
+	}
+
+	a := acs[0]
+
+	defer r.cleanup(context.TODO(), out, []Task{a}, []ExecEnv{execEnv})
+	return a.Exec(ctx, out)
+}
+
+func (r Runner) prepareAllActions(ctx context.Context, out io.Writer, allbuilds []graph.Artifact) ([]Task, error) {
+	preparedAcs := []Task{}
+	for _, execEnv := range r.orderedExecEnvs {
+		acsNames := r.acsByExecEnv[execEnv]
+		acs, err := execEnv.PrepareActions(ctx, out, allbuilds, acsNames)
+		if err != nil {
+			return nil, err
+		}
+
+		for _, a := range acs {
+			preparedAcs = append(preparedAcs, a)
+		}
+	}
+
+	return preparedAcs, nil
+}
+
+func (r Runner) cleanup(ctx context.Context, out io.Writer, ts []Task, execEnvs []ExecEnv) {
+	log.Entry(ctx).Debugf("Starting execution cleanup")
+	for _, t := range ts {
+		if t == nil {
+			continue
+		}
+		if err := t.Cleanup(ctx, out); err != nil {
+			// TODO(renzor): known issue related with Docker client deleting containers + prune will cause some
+			// warnings here. We need to fix https://github.com/GoogleContainerTools/skaffold/issues/8605
+			log.Entry(ctx).Warnf("%v cleanup error:%v", t.Name(), err)
+		}
+	}
+	for _, execEnv := range execEnvs {
+		if execEnv == nil {
+			continue
+		}
+		if err := execEnv.Cleanup(ctx, out); err != nil {
+			log.Entry(ctx).Warnf("Execution environment cleanup error:%v", err)
+		}
+	}
+}
+
+func getExecFunc(isFailFast bool) ExecStrategy {
+	if isFailFast {
+		return execWithFailingFast
+	}
+	return execWithFailingSafe
+}
+
+func execWithFailingFast(ctx context.Context, out io.Writer, ts []Task) error {
+	g, gCtx := errgroup.WithContext(ctx)
+
+	for _, t := range ts {
+		t := t
+		g.Go(func() error {
+			return execAndLog(gCtx, out, t)
+		})
+	}
+
+	return g.Wait()
+}
+
+func execWithFailingSafe(ctx context.Context, out io.Writer, ts []Task) error {
+	const maxWorkers = math.MaxInt64
+	g := semgroup.NewGroup(context.Background(), maxWorkers)
+
+	for _, t := range ts {
+		t := t
+		g.Go(func() error {
+			return execAndLog(ctx, out, t)
+		})
+	}
+
+	return g.Wait()
+}
+
+func execAndLog(ctx context.Context, out io.Writer, t Task) error {
+	log.Entry(ctx).Debugf("Starting execution for %v", t.Name())
+	err := t.Exec(ctx, out)
+	log.Entry(ctx).Debugf("Execution finished for %v", t.Name())
+	return err
+}
diff --git a/pkg/skaffold/actions/types.go b/pkg/skaffold/actions/types.go
new file mode 100644
index 000000000..64886b4e4
--- /dev/null
+++ b/pkg/skaffold/actions/types.go
@@ -0,0 +1,49 @@
+/*
+Copyright 2023 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 actions
+
+import (
+	"context"
+	"io"
+
+	"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/graph"
+)
+
+// ExecStrategy represents the functions to use to execute a list of tasks.
+type ExecStrategy func(ctx context.Context, out io.Writer, ts []Task) error
+
+// Task represents a single container from a custom action.
+type Task interface {
+	// Name is the unique name of the tasks across all other tasks and actions.
+	Name() string
+
+	// Exec triggers the execution of the task.
+	Exec(ctx context.Context, out io.Writer) error
+
+	// Cleanup frees the resources created by the task to execute itself.
+	Cleanup(ctx context.Context, out io.Writer) error
+}
+
+// ExecEnv represents every execution mode available for custom actions.
+type ExecEnv interface {
+	// PrepareActions creates the shared resources needed for the actions of an
+	// specific execution mode. It returns the actions of this execution mode.
+	PrepareActions(ctx context.Context, out io.Writer, allbuilds []graph.Artifact, acsNames []string) ([]Action, error)
+
+	// Cleanup frees the shared resources created during PrepareActions.
+	Cleanup(ctx context.Context, out io.Writer) error
+}
diff --git a/pkg/skaffold/config/options.go b/pkg/skaffold/config/options.go
index 17f53ac91..daacbb3d6 100644
--- a/pkg/skaffold/config/options.go
+++ b/pkg/skaffold/config/options.go
@@ -111,6 +111,7 @@ type SkaffoldOptions struct {
 	SyncRemoteCache             SyncRemoteCacheOption
 	WaitForDeletions            WaitForDeletions
 	ManifestsOverrides          []string
+	ManifestsValueFile          string
 }
 
 type RunMode string
diff --git a/pkg/skaffold/inspect/executionModes/list.go b/pkg/skaffold/inspect/executionModes/list.go
new file mode 100644
index 000000000..011129644
--- /dev/null
+++ b/pkg/skaffold/inspect/executionModes/list.go
@@ -0,0 +1,63 @@
+/*
+Copyright 2021 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 inspect
+
+import (
+	"context"
+	"io"
+
+	"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/config"
+	"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/inspect"
+)
+
+type executionModeList struct {
+	VerifyExecutionModes        map[string]string `json:"verifyExecutionModes"`
+	CustomActionsExecutionModes map[string]string `json:"customActionsExecutionModes"`
+}
+
+func PrintExecutionModesList(ctx context.Context, out io.Writer, opts inspect.Options, customActions []string) error {
+	formatter := inspect.OutputFormatter(out, opts.OutFormat)
+	cfgs, err := inspect.GetConfigSet(ctx, config.SkaffoldOptions{
+		ConfigurationFile:   opts.Filename,
+		ConfigurationFilter: opts.Modules,
+		RepoCacheDir:        opts.RepoCacheDir,
+		Profiles:            opts.Profiles,
+		PropagateProfiles:   opts.PropagateProfiles,
+	})
+	if err != nil {
+		formatter.WriteErr(err)
+		return err
+	}
+
+	l := &executionModeList{
+		VerifyExecutionModes:        map[string]string{},
+		CustomActionsExecutionModes: map[string]string{},
+	}
+
+	for _, c := range cfgs {
+		for _, tc := range c.Verify {
+			if tc.ExecutionMode.KubernetesClusterExecutionMode != nil {
+				l.VerifyExecutionModes[tc.Name] = "kubernetesCluster"
+			} else {
+				l.VerifyExecutionModes[tc.Name] = "local"
+			}
+		}
+	}
+	// TODO(#8572) add similar logic for customAction schema fields when they are complete
+
+	return formatter.Write(l)
+}
diff --git a/pkg/skaffold/instrumentation/export.go b/pkg/skaffold/instrumentation/export.go
index 43545581a..1ddf951cd 100644
--- a/pkg/skaffold/instrumentation/export.go
+++ b/pkg/skaffold/instrumentation/export.go
@@ -55,6 +55,7 @@ import (
 
 func ExportMetrics(exitCode int) error {
 	if !ShouldExportMetrics || meter.Command == "" {
+		log.Entry(context.TODO()).Debug("exporting metrics disabled")
 		return nil
 	}
 	home, err := homedir.Dir()
diff --git a/pkg/skaffold/instrumentation/firelog/exporter.go b/pkg/skaffold/instrumentation/firelog/exporter.go
index 3291eac2e..45ae24339 100644
--- a/pkg/skaffold/instrumentation/firelog/exporter.go
+++ b/pkg/skaffold/instrumentation/firelog/exporter.go
@@ -28,6 +28,9 @@ import (
 	"go.opentelemetry.io/otel/sdk/metric"
 	"go.opentelemetry.io/otel/sdk/metric/aggregation"
 	"go.opentelemetry.io/otel/sdk/metric/metricdata"
+
+	"github.com/GoogleContainerTools/skaffold/v2/fs"
+	"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/output/log"
 )
 
 var (
@@ -40,17 +43,23 @@ type Exporter struct {
 }
 
 func NewFireLogExporter() (metric.Exporter, error) {
-	if APIKey == "" {
-		// export metrics to std out if local env is set.
-		if _, ok := os.LookupEnv("SKAFFOLD_EXPORT_TO_STDOUT"); ok {
-			enc := json.NewEncoder(os.Stdout)
-			enc.SetIndent("", "  ")
-			exporter, err := stdoutmetric.New(stdoutmetric.WithEncoder(enc))
-			return exporter, err
-		}
-		return nil, nil
+	b, err := fs.AssetsFS.ReadFile("assets/firelog_generated/key.txt")
+	if err == nil {
+		APIKey = string(b)
+		return &Exporter{}, nil
+	}
+	log.Entry(context.TODO()).Debugf("failed to create firelog exporter due to error: %v", err)
+
+	// export metrics to std out if local env is set.
+	if _, ok := os.LookupEnv("SKAFFOLD_EXPORT_TO_STDOUT"); ok {
+		enc := json.NewEncoder(os.Stdout)
+		enc.SetIndent("", "  ")
+		exporter, err := stdoutmetric.New(stdoutmetric.WithEncoder(enc))
+		log.Entry(context.TODO()).Debugln("created a stdout exporter instead")
+		return exporter, err
 	}
-	return &Exporter{}, nil
+	log.Entry(context.TODO()).Debugln("did not create any log exporter")
+	return nil, nil
 }
 
 // Temporality returns the Temporality to use for an instrument kind.
diff --git a/pkg/skaffold/kubernetes/manifest/visitor.go b/pkg/skaffold/kubernetes/manifest/visitor.go
index 81fca664d..8103530e5 100644
--- a/pkg/skaffold/kubernetes/manifest/visitor.go
+++ b/pkg/skaffold/kubernetes/manifest/visitor.go
@@ -194,6 +194,10 @@ var TransformDenylist = map[apimachinery.GroupKind]latest.ResourceFilter{
 		GroupKind: "StatefulSet.apps",
 		Labels:    []string{".spec.volumeClaimTemplates.metadata.labels"},
 	},
+	{Group: "batch", Kind: "Job"}: {
+		GroupKind: "Job.batch",
+		Labels:    []string{".spec.template.metadata.labels"},
+	},
 }
 
 // FieldVisitor represents the aggregation/transformation that should be performed on each traversed field.
diff --git a/pkg/skaffold/kubernetes/status/resource/deployment.go b/pkg/skaffold/kubernetes/status/resource/deployment.go
index e766cdeaf..15238ad58 100644
--- a/pkg/skaffold/kubernetes/status/resource/deployment.go
+++ b/pkg/skaffold/kubernetes/status/resource/deployment.go
@@ -24,12 +24,16 @@ import (
 	"strings"
 	"time"
 
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/client-go/kubernetes"
+
 	"github.com/GoogleContainerTools/skaffold/v2/pkg/diag"
 	"github.com/GoogleContainerTools/skaffold/v2/pkg/diag/validator"
 	sErrors "github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/errors"
 	"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/event"
 	eventV2 "github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/event/v2"
 	"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/kubectl"
+	kubernetesclient "github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/kubernetes/client"
 	"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/output/log"
 	"github.com/GoogleContainerTools/skaffold/v2/proto/v1"
 	protoV2 "github.com/GoogleContainerTools/skaffold/v2/proto/v2"
@@ -210,11 +214,42 @@ func (r *Resource) checkRolloutStatus(ctx context.Context, cfg kubectl.Config) *
 	if ctx.Err() != nil {
 		return &proto.ActionableErr{ErrCode: proto.StatusCode_STATUSCHECK_USER_CANCELLED}
 	}
-
 	details := r.cleanupStatus(string(b))
+	if err != nil {
+		return parseKubectlRolloutError(details, r.deadline, r.tolerateFailures, err)
+	}
+
+	client, cErr := kubernetesclient.Client(cfg.GetKubeConfig())
+	if cErr != nil {
+		log.Entry(ctx).Debugf("error attempting to create kubernetes client for k8s event listing: %s", err)
+	} else {
+		err = checkK8sEventsForPodFailedCreateEvent(ctx, client, r.namespace, r.name)
+	}
+
+	// additional logic added here which checks kubernetes events to see if skaffold managed pod has a FailedCreatEvent
+	// this can be raised by an admission controller and if we don't error here, skaffold will wait for the pod to come up
+	// indefinitely even thought the admission controller has denied it
 	return parseKubectlRolloutError(details, r.deadline, r.tolerateFailures, err)
 }
 
+func checkK8sEventsForPodFailedCreateEvent(ctx context.Context, client kubernetes.Interface, namespace string, deploymentName string) error {
+	// Create a watcher for events
+	eventList, err := client.CoreV1().Events(namespace).List(ctx, metav1.ListOptions{})
+	if err != nil {
+		return fmt.Errorf("error attempting to list kubernetes events in namespace: %s, %w", namespace, err)
+	}
+
+	for _, event := range eventList.Items {
+		if event.Reason == "FailedCreate" {
+			if strings.HasPrefix(event.InvolvedObject.Name, deploymentName+"-") {
+				errMsg := fmt.Sprintf("Failed to create Pod for Deployment %s: %s\n", deploymentName, event.Message)
+				return fmt.Errorf(errMsg)
+			}
+		}
+	}
+	return nil
+}
+
 func (r *Resource) CheckStatus(ctx context.Context, cfg kubectl.Config) {
 	var ae *proto.ActionableErr
 	switch r.rType {
diff --git a/pkg/skaffold/render/renderer/helm/helm.go b/pkg/skaffold/render/renderer/helm/helm.go
index f473dd29b..747b77de7 100644
--- a/pkg/skaffold/render/renderer/helm/helm.go
+++ b/pkg/skaffold/render/renderer/helm/helm.go
@@ -125,6 +125,11 @@ func (h Helm) generateHelmManifests(ctx context.Context, builds []graph.Artifact
 			return nil, helm.UserErr(fmt.Sprintf("cannot expand release name %q", release.Name), err)
 		}
 
+		release.ChartPath, err = sUtil.ExpandEnvTemplateOrFail(release.ChartPath, nil)
+		if err != nil {
+			return nil, helm.UserErr(fmt.Sprintf("cannot expand chart path %q", release.ChartPath), err)
+		}
+
 		args := []string{"template", releaseName, helm.ChartSource(release)}
 		args = append(args, postRendererArgs...)
 		if release.Packaged == nil && release.Version != "" {
diff --git a/pkg/skaffold/verify/util/util.go b/pkg/skaffold/runner/actions.go
similarity index 60%
rename from pkg/skaffold/verify/util/util.go
rename to pkg/skaffold/runner/actions.go
index a1d57782a..372a3c570 100644
--- a/pkg/skaffold/verify/util/util.go
+++ b/pkg/skaffold/runner/actions.go
@@ -14,23 +14,15 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-package util
+package runner
 
 import (
-	"os"
+	"context"
+	"io"
 
-	"github.com/joho/godotenv"
+	"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/graph"
 )
 
-// ParseEnvVariablesFromFile takes an env file at the given filepath `fp` and parse the env values into a map
-func ParseEnvVariablesFromFile(fp string) (map[string]string, error) {
-	f, err := os.Open(fp)
-	if err != nil {
-		return nil, err
-	}
-	envMap, err := godotenv.Parse(f)
-	if err != nil {
-		return nil, err
-	}
-	return envMap, nil
+func (r *SkaffoldRunner) Exec(ctx context.Context, out io.Writer, artifacts []graph.Artifact, action string) error {
+	return r.actionsRunner.Exec(ctx, out, artifacts, action)
 }
diff --git a/pkg/skaffold/runner/actions_runner.go b/pkg/skaffold/runner/actions_runner.go
new file mode 100644
index 000000000..3b8ab3e0d
--- /dev/null
+++ b/pkg/skaffold/runner/actions_runner.go
@@ -0,0 +1,114 @@
+/*
+Copyright 2023 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 runner
+
+import (
+	"context"
+	"io"
+
+	"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/actions"
+	"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/actions/docker"
+	"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/deploy/label"
+	"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/graph"
+	"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/runner/runcontext"
+	"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/schema/latest"
+	"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/util"
+	vutil "github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/verify/util"
+)
+
+// ActionsRunner defines the API used to run custom actions.
+type ActionsRunner interface {
+	// Exec triggers the execution of the given action.
+	Exec(ctx context.Context, out io.Writer, allbuilds []graph.Artifact, action string) error
+
+	// ExecAll triggers the execution of all the defined actions.
+	ExecAll(ctx context.Context, out io.Writer, allbuilds []graph.Artifact) error
+}
+
+func GetActionsRunner(ctx context.Context, runCtx *runcontext.RunContext, l *label.DefaultLabeller, dockerNetwork string, envFile string) (ActionsRunner, error) {
+	aCfgs := []latest.Action{}
+
+	for _, p := range runCtx.GetPipelines() {
+		aCfgs = append(aCfgs, p.CustomActions...)
+	}
+
+	envMap, err := loadEnvMap(envFile)
+	if err != nil {
+		return nil, err
+	}
+
+	return createActionsRunner(ctx, runCtx, l, dockerNetwork, envMap, aCfgs)
+}
+
+func createActionsRunner(ctx context.Context, runCtx *runcontext.RunContext, l *label.DefaultLabeller, dNetwork string, envMap map[string]string, aCfgs []latest.Action) (ActionsRunner, error) {
+	execEnvByAction := map[string]actions.ExecEnv{}
+	ordExecEnvs := []actions.ExecEnv{}
+	acsByExecEnv := map[actions.ExecEnv][]string{}
+
+	pF := runCtx.PortForwardResources()
+	dockerCfgs, _ := cfgsByExecMode(aCfgs)
+
+	if len(dockerCfgs) > 0 {
+		dExecEnv, err := docker.NewExecEnv(ctx, runCtx, l, pF, dNetwork, envMap, dockerCfgs)
+		if err != nil {
+			return nil, err
+		}
+
+		ordExecEnvs = append(ordExecEnvs, dExecEnv)
+		insertExecEnv(execEnvByAction, dockerCfgs, dExecEnv)
+		for _, cfg := range dockerCfgs {
+			acsByExecEnv[dExecEnv] = append(acsByExecEnv[dExecEnv], cfg.Name)
+		}
+	}
+
+	return actions.NewRunner(execEnvByAction, ordExecEnvs, acsByExecEnv), nil
+}
+
+func cfgsByExecMode(aCfgs []latest.Action) (dockerCfgs []latest.Action, k8sCfgs []latest.Action) {
+	for _, cfg := range aCfgs {
+		setDefaultConfigValues(&cfg)
+		if cfg.ExecutionModeConfig.KubernetesClusterExecutionMode != nil {
+			k8sCfgs = append(k8sCfgs, cfg)
+			continue
+		}
+		dockerCfgs = append(dockerCfgs, cfg)
+	}
+	return
+}
+
+func setDefaultConfigValues(cfg *latest.Action) {
+	if cfg.Config.IsFailFast == nil {
+		cfg.Config.IsFailFast = util.Ptr(true)
+	}
+
+	if cfg.Config.Timeout == nil {
+		cfg.Config.Timeout = util.Ptr(0) // No timeout
+	}
+}
+
+func insertExecEnv(execEnvByAction map[string]actions.ExecEnv, acs []latest.Action, execEnv actions.ExecEnv) {
+	for _, a := range acs {
+		execEnvByAction[a.Name] = execEnv
+	}
+}
+
+func loadEnvMap(envFile string) (map[string]string, error) {
+	if envFile == "" {
+		return nil, nil
+	}
+	return vutil.ParseEnvVariablesFromFile(envFile)
+}
diff --git a/pkg/skaffold/runner/new.go b/pkg/skaffold/runner/new.go
index 44a91e29e..35d46b3d6 100644
--- a/pkg/skaffold/runner/new.go
+++ b/pkg/skaffold/runner/new.go
@@ -111,6 +111,13 @@ func NewForConfig(ctx context.Context, runCtx *runcontext.RunContext) (*Skaffold
 		return nil, fmt.Errorf("creating verifier: %w", err)
 	}
 
+	var acsRunner ActionsRunner
+	acsRunner, err = GetActionsRunner(ctx, runCtx, labeller, runCtx.VerifyDockerNetwork(), runCtx.Opts.VerifyEnvFile)
+	if err != nil {
+		endTrace(instrumentation.TraceEndError(err))
+		return nil, fmt.Errorf("creating actiosn runner: %w", err)
+	}
+
 	depLister := func(ctx context.Context, artifact *latest.Artifact) ([]string, error) {
 		ctx, endTrace := instrumentation.StartTrace(ctx, "NewForConfig_depLister")
 		defer endTrace()
@@ -176,6 +183,7 @@ func NewForConfig(ctx context.Context, runCtx *runcontext.RunContext) (*Skaffold
 		intents:            intents,
 		isLocalImage:       isLocalImage,
 		verifier:           verifier,
+		actionsRunner:      acsRunner,
 	}, nil
 }
 
diff --git a/pkg/skaffold/runner/renderer.go b/pkg/skaffold/runner/renderer.go
index b1eeee87e..9b14c2944 100644
--- a/pkg/skaffold/runner/renderer.go
+++ b/pkg/skaffold/runner/renderer.go
@@ -24,7 +24,7 @@ import (
 	"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/render/renderer/helm"
 	"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/runner/runcontext"
 	"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/schema/latest"
-	pkgutil "github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/util"
+	"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/util"
 )
 
 // GetRenderer creates a renderer from a given RunContext and pipeline definitions.
@@ -32,9 +32,22 @@ func GetRenderer(ctx context.Context, runCtx *runcontext.RunContext, hydrationDi
 	configNames := runCtx.Pipelines.AllOrderedConfigNames()
 
 	var gr renderer.GroupRenderer
+	var err error
 	for _, configName := range configNames {
 		p := runCtx.Pipelines.GetForConfigName(configName)
-		rs, err := renderer.New(ctx, runCtx, p.Render, hydrationDir, labels, configName, pkgutil.EnvSliceToMap(runCtx.Opts.ManifestsOverrides, "="))
+		mkvMap := map[string]string{}
+		if runCtx.Opts.ManifestsValueFile != "" {
+			mkvMap, err = util.ParseEnvVariablesFromFile(runCtx.Opts.ManifestsValueFile)
+			if err != nil {
+				return nil, err
+			}
+		}
+		overridesMap := util.EnvSliceToMap(runCtx.Opts.ManifestsOverrides, "=")
+		for k := range overridesMap {
+			mkvMap[k] = overridesMap[k]
+		}
+
+		rs, err := renderer.New(ctx, runCtx, p.Render, hydrationDir, labels, configName, mkvMap)
 		if err != nil {
 			return nil, err
 		}
diff --git a/pkg/skaffold/runner/runner.go b/pkg/skaffold/runner/runner.go
index 6f94c44cb..9f610a66a 100644
--- a/pkg/skaffold/runner/runner.go
+++ b/pkg/skaffold/runner/runner.go
@@ -59,6 +59,8 @@ type Runner interface {
 	Test(context.Context, io.Writer, []graph.Artifact) error
 	Verify(context.Context, io.Writer, []graph.Artifact) error
 	VerifyAndLog(context.Context, io.Writer, []graph.Artifact) error
+
+	Exec(context.Context, io.Writer, []graph.Artifact, string) error
 }
 
 // SkaffoldRunner is responsible for running the skaffold build, test and deploy config.
@@ -67,11 +69,12 @@ type SkaffoldRunner struct {
 	Pruner
 	tester test.Tester
 
-	renderer renderer.Renderer
-	deployer deploy.Deployer
-	verifier verify.Verifier
-	monitor  filemon.Monitor
-	listener Listener
+	renderer      renderer.Renderer
+	deployer      deploy.Deployer
+	verifier      verify.Verifier
+	actionsRunner ActionsRunner
+	monitor       filemon.Monitor
+	listener      Listener
 
 	cache              cache.Cache
 	changeSet          ChangeSet
diff --git a/pkg/skaffold/runner/verifier.go b/pkg/skaffold/runner/verifier.go
index bd019ce67..7130f1eb3 100644
--- a/pkg/skaffold/runner/verifier.go
+++ b/pkg/skaffold/runner/verifier.go
@@ -22,10 +22,10 @@ import (
 	"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/deploy/label"
 	"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/runner/runcontext"
 	"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/schema/latest"
+	"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/util"
 	"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/verify"
 	"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/verify/docker"
 	"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/verify/k8sjob"
-	"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/verify/util"
 )
 
 // GetVerifier creates a verifier from a given RunContext and deploy pipeline definitions.
@@ -53,7 +53,7 @@ func GetVerifier(ctx context.Context, runCtx *runcontext.RunContext, labeller *l
 	}
 
 	if len(kubernetesTestCases) != 0 {
-		nv, err := k8sjob.NewVerifier(ctx, runCtx, labeller, kubernetesTestCases, runCtx.Artifacts(), envMap)
+		nv, err := k8sjob.NewVerifier(ctx, runCtx, labeller, kubernetesTestCases, runCtx.Artifacts(), envMap, runCtx.GetNamespace())
 		if err != nil {
 			return nil, err
 		}
diff --git a/pkg/skaffold/util/util.go b/pkg/skaffold/util/util.go
index b3547910b..bad320cd2 100644
--- a/pkg/skaffold/util/util.go
+++ b/pkg/skaffold/util/util.go
@@ -30,6 +30,8 @@ import (
 	"sort"
 	"strings"
 
+	"github.com/joho/godotenv"
+
 	"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/output/log"
 	"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/walk"
 	"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/yaml"
@@ -348,3 +350,15 @@ func ExpandHomePath(path string) string {
 	}
 	return path
 }
+
+func ParseEnvVariablesFromFile(fp string) (map[string]string, error) {
+	f, err := os.Open(fp)
+	if err != nil {
+		return nil, err
+	}
+	envMap, err := godotenv.Parse(f)
+	if err != nil {
+		return nil, err
+	}
+	return envMap, nil
+}
diff --git a/pkg/skaffold/verify/docker/verify.go b/pkg/skaffold/verify/docker/verify.go
index 0875f0e3f..7a7166a6c 100644
--- a/pkg/skaffold/verify/docker/verify.go
+++ b/pkg/skaffold/verify/docker/verify.go
@@ -118,11 +118,14 @@ func (v *Verifier) TrackContainerFromBuild(artifact graph.Artifact, container tr
 // from each specified image, executing them, and waiting for execution to complete.
 func (v *Verifier) Verify(ctx context.Context, out io.Writer, allbuilds []graph.Artifact) error {
 	var err error
-	v.once.Do(func() {
-		err = v.client.NetworkCreate(ctx, v.network)
-	})
-	if err != nil {
-		return fmt.Errorf("creating skaffold network %s: %w", v.network, err)
+
+	if !v.networkFlagPassed {
+		v.once.Do(func() {
+			err = v.client.NetworkCreate(ctx, v.network)
+		})
+		if err != nil {
+			return fmt.Errorf("creating skaffold network %s: %w", v.network, err)
+		}
 	}
 
 	builds := []graph.Artifact{}
diff --git a/pkg/skaffold/verify/k8sjob/verify.go b/pkg/skaffold/verify/k8sjob/verify.go
index 53f79be02..e746e6884 100644
--- a/pkg/skaffold/verify/k8sjob/verify.go
+++ b/pkg/skaffold/verify/k8sjob/verify.go
@@ -22,8 +22,6 @@ import (
 	"io"
 	"io/ioutil"
 	"math"
-	"os/exec"
-	"strings"
 	"sync"
 	"time"
 
@@ -53,7 +51,6 @@ import (
 	"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/log"
 	"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/schema/latest"
 	"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/status"
-	"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/util"
 )
 
 // Verifier verifies deployments using kubernetes libs/CLI.
@@ -75,15 +72,7 @@ type Verifier struct {
 
 // NewVerifier returns a new Verifier for a VerifyConfig filled
 // with the needed configuration for `kubectl apply`
-func NewVerifier(ctx context.Context, cfg kubectl.Config, labeller *label.DefaultLabeller, testCases []*latest.VerifyTestCase, artifacts []*latest.Artifact, envMap map[string]string) (*Verifier, error) {
-	defaultNamespace := ""
-	b, err := (&util.Commander{}).RunCmdOut(context.Background(), exec.Command("kubectl", "config", "view", "--minify", "-o", "jsonpath='{..namespace}'"))
-	if err == nil {
-		defaultNamespace = strings.Trim(string(b), "'")
-		if defaultNamespace == "default" {
-			defaultNamespace = ""
-		}
-	}
+func NewVerifier(ctx context.Context, cfg kubectl.Config, labeller *label.DefaultLabeller, testCases []*latest.VerifyTestCase, artifacts []*latest.Artifact, envMap map[string]string, defaultNamespace string) (*Verifier, error) {
 	kubectl := kubectl.NewCLI(cfg, latest.KubectlFlags{}, defaultNamespace)
 	// default namespace must be "default" not "" when used to create and stream logs from Job(s)
 	if defaultNamespace == "" {
@@ -197,7 +186,6 @@ func (v *Verifier) Verify(ctx context.Context, out io.Writer, allbuilds []graph.
 func (v *Verifier) createAndRunJob(ctx context.Context, tc latest.VerifyTestCase) error {
 	// TODO(aaron-prindle) look for and delete existing job w/ same name?
 	// - must be done before logger starts or else confusing output
-
 	clientset, err := kubernetesclient.Client(v.kubectl.KubeContext)
 	if err != nil {
 		return fmt.Errorf("getting Kubernetes client: %w", err)


Test patch:
diff --git a/.github/workflows/unit-tests-darwin.yml b/.github/workflows/unit-tests-darwin.yml
index 0470c923f..15f8233bf 100644
--- a/.github/workflows/unit-tests-darwin.yml
+++ b/.github/workflows/unit-tests-darwin.yml
@@ -23,7 +23,7 @@ jobs:
     - name: Set up Go
       uses: actions/setup-go@v4
       with:
-        go-version: 1.19.*
+        go-version: 1.20.*
       id: go
 
     # Retrieve build locations with `go env`
diff --git a/.github/workflows/unit-tests-linux.yml b/.github/workflows/unit-tests-linux.yml
index 95ca12b65..859744977 100644
--- a/.github/workflows/unit-tests-linux.yml
+++ b/.github/workflows/unit-tests-linux.yml
@@ -17,7 +17,7 @@ jobs:
     - name: Set up Go
       uses: actions/setup-go@v4
       with:
-        go-version: 1.19.*
+        go-version: 1.20.*
       id: go
 
     - name: Check out code into the Go module directory
diff --git a/.github/workflows/unit-tests-windows.yml b/.github/workflows/unit-tests-windows.yml
index 616ed2e94..eb49c3360 100644
--- a/.github/workflows/unit-tests-windows.yml
+++ b/.github/workflows/unit-tests-windows.yml
@@ -24,7 +24,7 @@ jobs:
     - name: Set up Go
       uses: actions/setup-go@v4
       with:
-        go-version: 1.19.*
+        go-version: 1.20.*
       id: go
 
     # Retrieve build locations with `go env`
diff --git a/integration/binauthz_test.go b/integration/binauthz_test.go
new file mode 100644
index 000000000..5205bfe98
--- /dev/null
+++ b/integration/binauthz_test.go
@@ -0,0 +1,48 @@
+/*
+Copyright 2023 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 (
+	"os"
+	"strings"
+	"testing"
+
+	"github.com/GoogleContainerTools/skaffold/v2/integration/skaffold"
+)
+
+const (
+	binauthzClusterName = "integration-tests-binauthz"
+)
+
+// TestBinAuthZWithDeploy targets the integration-tests-binauthz cluster on k8s-skaffold which has '--binauthz-evaluation-mode=PROJECT_SINGLETON_POLICY_ENFORCE' set
+func TestBinAuthZWithDeploy(t *testing.T) {
+	MarkIntegrationTest(t, NeedsGcp)
+	if os.Getenv("GKE_CLUSTER_NAME") != binauthzClusterName {
+		t.Skip("TestBinAuthZWithDeploy only runs against integration-tests-binauthz cluster")
+	}
+
+	ns, _ := SetupNamespace(t)
+	expected := "Failed to create Pod for Deployment"
+
+	// We're not providing a tag for the getting-started image
+	output, err := skaffold.Deploy("--images", "index.docker.io/library/busybox:1", "--default-repo=").InDir("examples/kustomize").InNs(ns.Name).RunWithCombinedOutput(t)
+	if err == nil {
+		t.Errorf("expected to see an error since the image deployed to cluster should be denied based on binauthz policy: %s", output)
+	} else if !strings.Contains(string(output), expected) {
+		t.Errorf("unexpected error text - expected: %s, got: %s", expected, output)
+	}
+}
diff --git a/integration/render_test.go b/integration/render_test.go
index bcff39f8a..b691d211c 100644
--- a/integration/render_test.go
+++ b/integration/render_test.go
@@ -1171,21 +1171,20 @@ metadata:
   name: getting-started
   labels:
     a: hhhh # kpt-set: ${app1}
-spec:
-  containers:
-  - name: getting-started
-    image: skaffold-example`,
-			}, expectedOut: `apiVersion: v1
-		kind: Pod
-		metadata:
-		 name: getting-started
-		 labels:
-		   a: from-command-line
-		spec:
-		 containers:
-		 - name: getting-started
-		   image: skaffold-example
-`,
+  spec:
+    containers:
+      - name: getting-started
+        image: skaffold-example`},
+			expectedOut: `apiVersion: v1
+kind: Pod
+metadata:
+  name: getting-started
+  labels:
+    a: from-command-line
+  spec:
+    containers:
+      - name: getting-started
+        image: skaffold-example`,
 		},
 		{description: "kustomize set annotation with set-annotations transformer",
 			args: []string{"--offline=true"},
@@ -1221,8 +1220,8 @@ spec:
         app: kustomize-test
     spec:
       containers:
-      - name: kustomize-test
-        image: not/a/valid/image
+        - name: kustomize-test
+          image: not/a/valid/image
 `, "patch.yaml": `apiVersion: apps/v1
 kind: Deployment
 metadata:
@@ -1231,38 +1230,38 @@ spec:
   template:
     spec:
       containers:
-      - name: kustomize-test
-        image: index.docker.io/library/busybox
-        command:
-          - sleep
-          - "3600"
+        - name: kustomize-test
+          image: index.docker.io/library/busybox
+          command:
+            - sleep
+            - "3600"
 `},
 			expectedOut: `apiVersion: apps/v1
-		kind: Deployment
-		metadata:
-		 annotations:
-		   author: fake-author
-		 labels:
-		   app: kustomize-test
-		 name: kustomize-test
-		spec:
-		 replicas: 1
-		 selector:
-		   matchLabels:
-		     app: kustomize-test
-		 template:
-		   metadata:
-		     annotations:
-		       author: fake-author
-		     labels:
-		       app: kustomize-test
-		   spec:
-		     containers:
-		     - command:
-		       - sleep
-		       - "3600"
-		       image: index.docker.io/library/busybox
-		       name: kustomize-test
+kind: Deployment
+metadata:
+  annotations:
+    author: fake-author
+  labels:
+    app: kustomize-test
+  name: kustomize-test
+spec:
+  replicas: 1
+  selector:
+    matchLabels:
+      app: kustomize-test
+  template:
+    metadata:
+      annotations:
+        author: fake-author
+      labels:
+        app: kustomize-test
+    spec:
+      containers:
+        - command:
+            - sleep
+            - "3600"
+          image: index.docker.io/library/busybox
+          name: kustomize-test
 `},
 		{
 			description: "kustomize/overlay parameterization with --set flag",
@@ -1338,6 +1337,122 @@ spec:
         name: skaffold-kustomize
 `,
 		},
+		{description: "test set transformer values with value file",
+			args: []string{"--offline=true", "--set-value-file", "values.env"},
+			config: `
+apiVersion: skaffold/v4beta2
+kind: Config
+manifests:
+  rawYaml:
+    - k8s-pod.yaml
+  transform:
+    - name: apply-setters
+      configMap:
+        - "app1:from-apply-setters-1"
+`,
+			input: map[string]string{
+				"k8s-pod.yaml": `
+apiVersion: v1
+kind: Pod
+metadata:
+  name: getting-started
+  labels:
+    a: hhhh # kpt-set: ${app1}
+spec:
+  containers:
+    - name: getting-started
+      image: skaffold-example`,
+				"values.env": `app1=from-file`,
+			}, expectedOut: `apiVersion: v1
+kind: Pod
+metadata:
+  name: getting-started
+  labels:
+    a: from-file
+spec:
+  containers:
+    - name: getting-started
+      image: skaffold-example
+`,
+		},
+		{
+			description: "test set transformer values with value file and value from command-line",
+			args:        []string{"--offline=true", "--set-value-file", "values.env", "--set", "app2=from-command-line"},
+			config: `
+apiVersion: skaffold/v4beta2
+kind: Config
+manifests:
+  rawYaml:
+    - k8s-pod.yaml
+  transform:
+    - name: apply-setters
+      configMap:
+        - "app1:from-apply-setters-1"
+`,
+			input: map[string]string{
+				"k8s-pod.yaml": `
+apiVersion: v1
+kind: Pod
+metadata:
+  name: getting-started
+  labels:
+    a: hhhh # kpt-set: ${app1}
+    b: hhhh # kpt-set: ${app2}
+spec:
+  containers:
+    - name: getting-started
+      image: skaffold-example`,
+				"values.env": `app1=from-file`,
+			}, expectedOut: `apiVersion: v1
+kind: Pod
+metadata:
+  name: getting-started
+  labels:
+    a: from-file
+    b: from-command-line
+spec:
+  containers:
+    - name: getting-started
+      image: skaffold-example
+`},
+		{
+			description: "test set transformer file values respect values from command-line",
+			args:        []string{"--offline=true", "--set-value-file", "values.env", "--set", "app1=from-command-line"},
+			config: `
+apiVersion: skaffold/v4beta2
+kind: Config
+manifests:
+  rawYaml:
+    - k8s-pod.yaml
+  transform:
+    - name: apply-setters
+      configMap:
+        - "app1:from-apply-setters-1"
+`,
+			input: map[string]string{
+				"k8s-pod.yaml": `
+apiVersion: v1
+kind: Pod
+metadata:
+  name: getting-started
+  labels:
+    a: hhhh # kpt-set: ${app1}
+spec:
+  containers:
+    - name: getting-started
+      image: skaffold-example`,
+				"values.env": `app1=from-file`,
+			}, expectedOut: `apiVersion: v1
+kind: Pod
+metadata:
+  name: getting-started
+  labels:
+    a: from-command-line
+spec:
+  containers:
+    - name: getting-started
+      image: skaffold-example
+`},
 	}
 
 	for _, test := range tests {
@@ -1352,18 +1467,8 @@ spec:
 
 			tmpDir.Chdir()
 			output := skaffold.Render(test.args...).RunOrFailOutput(t.T)
-			var out map[string]any
-			var ex map[string]any
-			err := yaml.Unmarshal(output, &out)
-			if err != nil {
-				return
-			}
-			err = yaml.Unmarshal([]byte(test.expectedOut), &ex)
-			if err != nil {
-				return
-			}
 
-			t.CheckDeepEqual(ex, out)
+			t.CheckDeepEqual(test.expectedOut, string(output), testutil.YamlObj(t.T))
 		})
 	}
 }
diff --git a/integration/verify_test.go b/integration/verify_test.go
index c147b998d..ac514ba3e 100644
--- a/integration/verify_test.go
+++ b/integration/verify_test.go
@@ -56,6 +56,14 @@ func TestLocalVerifyPassingTestsWithEnvVar(t *testing.T) {
 	// TODO(aaron-prindle) verify that SUCCEEDED event is found where expected
 }
 
+func TestVerifyWithNotCreatedNetwork(t *testing.T) {
+	MarkIntegrationTest(t, CanRunWithoutGcp)
+	// `--default-repo=` is used to cancel the default repo that is set by default.
+	logs, err := skaffold.Verify("--default-repo=", "--env-file", "verify.env", "--docker-network", "not-created-network").InDir("testdata/verify-succeed").RunWithCombinedOutput(t)
+	testutil.CheckError(t, true, err)
+	testutil.CheckContains(t, "network not-created-network not found", string(logs))
+}
+
 func TestLocalVerifyOneTestFailsWithEnvVar(t *testing.T) {
 	MarkIntegrationTest(t, CanRunWithoutGcp)
 	tmp := t.TempDir()
diff --git a/pkg/diag/diag_test.go b/pkg/diag/diag_test.go
index ab27a6e53..a214fbbc2 100644
--- a/pkg/diag/diag_test.go
+++ b/pkg/diag/diag_test.go
@@ -34,9 +34,7 @@ type mockValidator struct {
 	listOptions metav1.ListOptions
 }
 
-type mockErrValidator struct {
-	*mockValidator
-}
+type mockErrValidator struct{}
 
 func (m *mockValidator) Validate(_ context.Context, ns string, opts metav1.ListOptions) ([]validator.Resource, error) {
 	m.ns = append(m.ns, ns)
diff --git a/pkg/skaffold/actions/docker/exec_env_test.go b/pkg/skaffold/actions/docker/exec_env_test.go
new file mode 100644
index 000000000..31b83baed
--- /dev/null
+++ b/pkg/skaffold/actions/docker/exec_env_test.go
@@ -0,0 +1,205 @@
+/*
+Copyright 2023 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 docker
+
+import (
+	"context"
+	"io"
+	"testing"
+
+	v1 "github.com/google/go-containerregistry/pkg/v1"
+
+	dockerport "github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/deploy/docker/port"
+	"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/deploy/label"
+	"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/docker"
+	"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/graph"
+	"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/schema/latest"
+	"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/util"
+	"github.com/GoogleContainerTools/skaffold/v2/testutil"
+)
+
+type fakeDockerDaemon struct {
+	docker.LocalDaemon
+	PulledImages []string
+	ImgsInDaemon map[string]string
+}
+
+func (fd *fakeDockerDaemon) NetworkCreate(ctx context.Context, name string) error {
+	return nil
+}
+
+func (fd *fakeDockerDaemon) Pull(ctx context.Context, out io.Writer, ref string, platform v1.Platform) error {
+	fd.PulledImages = append(fd.PulledImages, ref)
+	return nil
+}
+
+func (fd *fakeDockerDaemon) ImageID(ctx context.Context, ref string) (string, error) {
+	img := fd.ImgsInDaemon[ref]
+	return img, nil
+}
+
+func getActionCfg(aName string, containers []latest.VerifyContainer) latest.Action {
+	return latest.Action{
+		Name: aName,
+		Config: latest.ActionConfig{
+			IsFailFast: util.Ptr(true),
+			Timeout:    util.Ptr(0),
+		},
+		ExecutionModeConfig: latest.ActionExecutionModeConfig{
+			VerifyExecutionModeType: latest.VerifyExecutionModeType{
+				LocalExecutionMode: &latest.LocalVerifier{},
+			},
+		},
+		Containers: containers,
+	}
+}
+func TestExecEnv_PrepareActions(t *testing.T) {
+	tests := []struct {
+		description      string
+		actionToExec     string
+		expectedTasks    []string
+		shouldFail       bool
+		errMsg           string
+		availableAcsCfgs []latest.Action
+	}{
+		{
+			description:  "fail with action not found",
+			actionToExec: "not-created-action",
+			shouldFail:   true,
+			errMsg:       "action not-created-action not found for local execution mode",
+			availableAcsCfgs: []latest.Action{
+				getActionCfg("action1", []latest.VerifyContainer{}),
+			},
+		},
+		{
+			description:   "prepare action with two tasks",
+			actionToExec:  "action1",
+			expectedTasks: []string{"container1", "container2"},
+			availableAcsCfgs: []latest.Action{
+				getActionCfg("action1", []latest.VerifyContainer{
+					{Name: "container1", Image: "gcr.io/k8s-skaffold/mock:latest"},
+					{Name: "container2", Image: "gcr.io/k8s-skaffold/mock:latest"},
+				}),
+				getActionCfg("action2", []latest.VerifyContainer{
+					{Name: "container3", Image: "gcr.io/k8s-skaffold/mock:latest"},
+					{Name: "container4", Image: "gcr.io/k8s-skaffold/mock:latest"},
+				}),
+			},
+		},
+	}
+
+	for _, test := range tests {
+		testutil.Run(t, test.description, func(t *testutil.T) {
+			ctx := context.TODO()
+
+			// Mock Docker client.
+			t.Override(&docker.NewAPIClient, func(context.Context, docker.Config) (docker.LocalDaemon, error) {
+				return &fakeDockerDaemon{
+					LocalDaemon: docker.NewLocalDaemon(&testutil.FakeAPIClient{}, nil, false, nil),
+				}, nil
+			})
+
+			// Mock new tasks creation to track created actions.
+			var createdTasks []string
+			t.Override(&NewTask, func(c latest.VerifyContainer, client docker.LocalDaemon, pM *dockerport.PortManager, resources []*latest.PortForwardResource, artifact graph.Artifact, timeout int, execEnv *ExecEnv) Task {
+				createdTasks = append(createdTasks, c.Name)
+				return newTask(c, client, pM, resources, artifact, timeout, execEnv)
+			})
+
+			execEnv, err := NewExecEnv(ctx, nil, &label.DefaultLabeller{}, nil, "", nil, test.availableAcsCfgs)
+			t.CheckNoError(err)
+
+			acs, err := execEnv.PrepareActions(ctx, nil, nil, []string{test.actionToExec})
+
+			if test.shouldFail {
+				t.CheckErrorContains(test.errMsg, err)
+			} else {
+				t.CheckNoError(err)
+				t.CheckDeepEqual(1, len(acs))
+				a := acs[0]
+				t.CheckDeepEqual(test.actionToExec, a.Name())
+				t.CheckDeepEqual(test.expectedTasks, createdTasks)
+			}
+		})
+	}
+}
+
+func TestExecEnv_PullImages(t *testing.T) {
+	tests := []struct {
+		description        string
+		actionToExec       string
+		availableAcsCfgs   []latest.Action
+		expectedPulledImgs []string
+		builtImgs          []graph.Artifact
+		imagesInDaemon     map[string]string
+	}{
+		{
+			description:  "prepare action with two images to pull",
+			actionToExec: "action1",
+			availableAcsCfgs: []latest.Action{
+				getActionCfg("action1", []latest.VerifyContainer{
+					{Name: "container1", Image: "gcr.io/k8s-skaffold/mock1:latest"},
+					{Name: "container2", Image: "gcr.io/k8s-skaffold/mock2:latest"},
+				}),
+			},
+			expectedPulledImgs: []string{"gcr.io/k8s-skaffold/mock1:latest", "gcr.io/k8s-skaffold/mock2:latest"},
+		},
+		{
+			description:  "prepare action with one image to pull and other already built and present in daemon",
+			actionToExec: "action1",
+			availableAcsCfgs: []latest.Action{
+				getActionCfg("action1", []latest.VerifyContainer{
+					{Name: "container1", Image: "mock1"},
+					{Name: "container2", Image: "mock2:latest"},
+				}),
+			},
+			expectedPulledImgs: []string{"mock2:latest"},
+			builtImgs: []graph.Artifact{
+				{
+					ImageName: "mock1",
+					Tag:       "mock1:latest",
+				},
+			},
+			imagesInDaemon: map[string]string{
+				"mock1:latest": "id1234",
+			},
+		},
+	}
+
+	for _, test := range tests {
+		testutil.Run(t, test.description, func(t *testutil.T) {
+			ctx := context.TODO()
+
+			// Mock Docker client.
+			fDockerDaemon := &fakeDockerDaemon{
+				LocalDaemon:  docker.NewLocalDaemon(&testutil.FakeAPIClient{}, nil, false, nil),
+				ImgsInDaemon: test.imagesInDaemon,
+			}
+			t.Override(&docker.NewAPIClient, func(context.Context, docker.Config) (docker.LocalDaemon, error) {
+				return fDockerDaemon, nil
+			})
+
+			execEnv, err := NewExecEnv(ctx, nil, &label.DefaultLabeller{}, nil, "", nil, test.availableAcsCfgs)
+			t.CheckNoError(err)
+
+			_, err = execEnv.PrepareActions(ctx, nil, test.builtImgs, []string{test.actionToExec})
+			t.CheckNoError(err)
+
+			t.CheckDeepEqual(test.expectedPulledImgs, fDockerDaemon.PulledImages)
+		})
+	}
+}
diff --git a/pkg/skaffold/actions/runner_test.go b/pkg/skaffold/actions/runner_test.go
new file mode 100644
index 000000000..592b6e373
--- /dev/null
+++ b/pkg/skaffold/actions/runner_test.go
@@ -0,0 +1,369 @@
+/*
+Copyright 2023 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 actions
+
+import (
+	"context"
+	"fmt"
+	"io"
+	"testing"
+	"time"
+
+	"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/graph"
+	"github.com/GoogleContainerTools/skaffold/v2/testutil"
+)
+
+type mockTask struct {
+	ExecF    func(ctx context.Context, out io.Writer, m *mockTask) error
+	Finished bool
+}
+
+func (ma *mockTask) Name() string { return "" }
+
+func (ma *mockTask) Exec(ctx context.Context, out io.Writer) error {
+	if ma.ExecF != nil {
+		return ma.ExecF(ctx, out, ma)
+	}
+	ma.Finished = true
+	return nil
+}
+
+func (ma *mockTask) Cleanup(ctx context.Context, out io.Writer) error { return nil }
+
+type mockExecEnv struct {
+	Actions     []string
+	MockPrepAcs func(ctx context.Context, out io.Writer, allbuilds []graph.Artifact, acsNames []string) ([]Action, error)
+	MockActions []Action
+}
+
+func (me mockExecEnv) PrepareActions(ctx context.Context, out io.Writer, allbuilds []graph.Artifact, acsNames []string) ([]Action, error) {
+	if me.MockPrepAcs != nil {
+		return me.MockPrepAcs(ctx, out, allbuilds, acsNames)
+	}
+	return me.MockActions, nil
+}
+
+func (me mockExecEnv) Cleanup(ctx context.Context, out io.Writer) error {
+	return nil
+}
+
+func newRunner(mExecEnvs []mockExecEnv) Runner {
+	execEnvByAction := map[string]ExecEnv{}
+	acsByExecEnv := map[ExecEnv][]string{}
+	execEnvs := []ExecEnv{}
+
+	for _, execEnv := range mExecEnvs {
+		for _, a := range execEnv.Actions {
+			execEnvByAction[a] = &execEnv
+			acsByExecEnv[&execEnv] = append(acsByExecEnv[&execEnv], a)
+		}
+		execEnvs = append(execEnvs, execEnv)
+	}
+
+	return NewRunner(execEnvByAction, execEnvs, acsByExecEnv)
+}
+
+func TestActionsRunner_Exec(t *testing.T) {
+	tests := []struct {
+		description  string
+		actionToExec string
+		shouldErr    bool
+		err          string
+		execEnvs     []mockExecEnv
+	}{
+		{
+			description:  "action not found",
+			actionToExec: "action3",
+			shouldErr:    true,
+			err:          "custom action action3 not found",
+			execEnvs: []mockExecEnv{
+				{
+					Actions: []string{"action1", "action2"},
+				},
+			},
+		},
+		{
+			description:  "zero actions returned",
+			actionToExec: "action1",
+			shouldErr:    true,
+			err:          "failed to create action1 action",
+			execEnvs: []mockExecEnv{
+				{
+					Actions: []string{"action1", "action2"},
+					MockPrepAcs: func(ctx context.Context, out io.Writer, allbuilds []graph.Artifact, acsNames []string) ([]Action, error) {
+						return []Action{}, nil
+					},
+				},
+			},
+		},
+		{
+			description:  "more than one action returned",
+			actionToExec: "action1",
+			shouldErr:    true,
+			err:          "failed to create action1 action",
+			execEnvs: []mockExecEnv{
+				{
+					Actions: []string{"action1", "action2"},
+					MockPrepAcs: func(ctx context.Context, out io.Writer, allbuilds []graph.Artifact, acsNames []string) ([]Action, error) {
+						return []Action{{}, {}}, nil
+					},
+				},
+			},
+		},
+		{
+			description:  "forward exec error",
+			actionToExec: "action1",
+			shouldErr:    true,
+			err:          "error from action execution",
+			execEnvs: []mockExecEnv{
+				{
+					Actions: []string{"action1", "action2"},
+					MockPrepAcs: func(ctx context.Context, out io.Writer, allbuilds []graph.Artifact, acsNames []string) ([]Action, error) {
+						return []Action{
+							{
+								execFunc: func(ctx context.Context, out io.Writer, ts []Task) error {
+									return fmt.Errorf("error from action execution")
+								},
+							},
+						}, nil
+					},
+				},
+			},
+		},
+		{
+			description:  "forward prepare actions error",
+			actionToExec: "action1",
+			shouldErr:    true,
+			err:          "error from prepare actions",
+			execEnvs: []mockExecEnv{
+				{
+					Actions: []string{"action1", "action2"},
+					MockPrepAcs: func(ctx context.Context, out io.Writer, allbuilds []graph.Artifact, acsNames []string) ([]Action, error) {
+						return nil, fmt.Errorf("error from prepare actions")
+					},
+				},
+			},
+		},
+		{
+			description:  "exec successfully",
+			actionToExec: "action1",
+			execEnvs: []mockExecEnv{
+				{
+					Actions: []string{"action1", "action2"},
+					MockPrepAcs: func(ctx context.Context, out io.Writer, allbuilds []graph.Artifact, acsNames []string) ([]Action, error) {
+						return []Action{
+							{
+								execFunc: func(ctx context.Context, out io.Writer, ts []Task) error {
+									return nil
+								},
+							},
+						}, nil
+					},
+				},
+			},
+		},
+	}
+
+	for _, test := range tests {
+		testutil.Run(t, test.description, func(t *testutil.T) {
+			runner := newRunner(test.execEnvs)
+			err := runner.Exec(context.TODO(), nil, nil, test.actionToExec)
+
+			if test.shouldErr {
+				t.CheckErrorContains(test.err, err)
+			} else {
+				t.CheckNoError(err)
+			}
+		})
+	}
+}
+
+func TestActionsRunner_ExecFailFast(t *testing.T) {
+	tests := []struct {
+		description  string
+		actionToExec string
+		shouldErr    bool
+		err          string
+		action       Action
+	}{
+		{
+			description:  "interrupt other tasks when one fails",
+			actionToExec: "action1",
+			shouldErr:    true,
+			err:          "error from mock task",
+			action: Action{
+				tasks: []Task{
+					&mockTask{
+						// This task will fail, interrupting the others.
+						ExecF: func(ctx context.Context, out io.Writer, m *mockTask) error {
+							m.Finished = false
+							return fmt.Errorf("error from mock task")
+						},
+					},
+					&mockTask{
+						ExecF: func(ctx context.Context, out io.Writer, m *mockTask) error {
+							m.Finished = false
+							var err error
+							select {
+							// This task will run for 10 secs, but should be interrupted
+							// in 2 seconds due to the other task failed, so this never finish.
+							case <-time.After(time.Second * 5):
+								m.Finished = true
+							case <-ctx.Done():
+								err = ctx.Err()
+							}
+							return err
+						},
+					},
+				},
+			},
+		},
+		{
+			description:  "interrupt all tasks when on action timeout",
+			actionToExec: "action1",
+			shouldErr:    true,
+			err:          "context deadline",
+			action: Action{
+				timeout: 1,
+				tasks: []Task{
+					&mockTask{
+						ExecF: func(ctx context.Context, out io.Writer, m *mockTask) error {
+							m.Finished = false
+							var err error
+							select {
+							case <-time.After(time.Second * 2):
+								m.Finished = true
+							case <-ctx.Done():
+								err = ctx.Err()
+							}
+							return err
+						},
+					},
+					&mockTask{
+						ExecF: func(ctx context.Context, out io.Writer, m *mockTask) error {
+							m.Finished = false
+							var err error
+							select {
+							case <-time.After(time.Second * 2):
+								m.Finished = true
+							case <-ctx.Done():
+								err = ctx.Err()
+							}
+							return err
+						},
+					},
+				},
+			},
+		},
+		{
+			description:  "execute all tasks till end",
+			actionToExec: "action1",
+			shouldErr:    false,
+			action: Action{
+				tasks: []Task{&mockTask{}, &mockTask{}},
+			},
+		},
+	}
+
+	for _, test := range tests {
+		testutil.Run(t, test.description, func(t *testutil.T) {
+			test.action.execFunc = execWithFailingFast
+			execEnvs := []mockExecEnv{
+				{
+					Actions:     []string{test.actionToExec},
+					MockActions: []Action{test.action},
+				},
+			}
+
+			runner := newRunner(execEnvs)
+			err := runner.Exec(context.TODO(), nil, nil, test.actionToExec)
+
+			if test.shouldErr {
+				t.CheckErrorContains(test.err, err)
+				for _, task := range test.action.tasks {
+					t.CheckFalse(task.(*mockTask).Finished)
+				}
+			} else {
+				for _, task := range test.action.tasks {
+					t.CheckTrue(task.(*mockTask).Finished)
+				}
+				t.CheckNoError(err)
+			}
+		})
+	}
+}
+
+func TestActionsRunner_ExecFailSafe(t *testing.T) {
+	tests := []struct {
+		description  string
+		actionToExec string
+		shouldErr    bool
+		err          string
+		action       Action
+	}{
+		{
+			description:  "run all tasks till end if one fails",
+			actionToExec: "action1",
+			shouldErr:    true,
+			err:          "error from mock task",
+			action: Action{
+				tasks: []Task{
+					&mockTask{},
+					&mockTask{
+						// This task will fail first.
+						ExecF: func(ctx context.Context, out io.Writer, m *mockTask) error {
+							m.Finished = true
+							return fmt.Errorf("error from mock task")
+						},
+					},
+				},
+			},
+		},
+		{
+			description:  "execute all tasks till end",
+			actionToExec: "action1",
+			shouldErr:    false,
+			action: Action{
+				tasks: []Task{&mockTask{}, &mockTask{}},
+			},
+		},
+	}
+
+	for _, test := range tests {
+		testutil.Run(t, test.description, func(t *testutil.T) {
+			test.action.execFunc = execWithFailingFast
+			execEnvs := []mockExecEnv{
+				{
+					Actions:     []string{test.actionToExec},
+					MockActions: []Action{test.action},
+				},
+			}
+
+			runner := newRunner(execEnvs)
+			err := runner.Exec(context.TODO(), nil, nil, test.actionToExec)
+
+			if test.shouldErr {
+				t.CheckErrorContains(test.err, err)
+			} else {
+				t.CheckNoError(err)
+			}
+			for _, task := range test.action.tasks {
+				t.CheckTrue(task.(*mockTask).Finished)
+			}
+		})
+	}
+}
diff --git a/pkg/skaffold/deploy/helm/helm_test.go b/pkg/skaffold/deploy/helm/helm_test.go
index efb7e31f8..702984d7c 100644
--- a/pkg/skaffold/deploy/helm/helm_test.go
+++ b/pkg/skaffold/deploy/helm/helm_test.go
@@ -89,6 +89,17 @@ var testDeployEnvTemplateNamespacedConfig = latest.LegacyHelmDeploy{
 	}},
 }
 
+var testDeployWithTemplatedChartPath = latest.LegacyHelmDeploy{
+	Releases: []latest.HelmRelease{{
+		Name:      "skaffold-helm",
+		ChartPath: "examples/{{.FOO}}",
+		Overrides: schemautil.HelmOverrides{Values: map[string]interface{}{"foo": "bar"}},
+		SetValues: map[string]string{
+			"some.key": "somevalue",
+		},
+	}},
+}
+
 var testDeployConfigRemoteRepo = latest.LegacyHelmDeploy{
 	Releases: []latest.HelmRelease{{
 		Name:      "skaffold-helm",
@@ -1212,6 +1223,15 @@ func TestHelmRender(t *testing.T) {
 			helm:   testDeployWithTemplatedName,
 			builds: testBuilds,
 		},
+		{
+			description: "render with templated chart path",
+			shouldErr:   false,
+			commands: testutil.
+				CmdRun("helm --kube-context kubecontext dep build examples/FOOBAR --kubeconfig kubeconfig").
+				AndRun("helm --kube-context kubecontext template skaffold-helm examples/FOOBAR --post-renderer SKAFFOLD-BINARY --set some.key=somevalue --kubeconfig kubeconfig"),
+			helm:   testDeployWithTemplatedChartPath,
+			builds: testBuilds,
+		},
 		{
 			description: "render with namespace",
 			shouldErr:   false,
diff --git a/pkg/skaffold/inspect/executionModes/list_test.go b/pkg/skaffold/inspect/executionModes/list_test.go
new file mode 100644
index 000000000..a0c924a81
--- /dev/null
+++ b/pkg/skaffold/inspect/executionModes/list_test.go
@@ -0,0 +1,146 @@
+/*
+Copyright 2021 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 inspect
+
+import (
+	"bytes"
+	"context"
+	"errors"
+	"fmt"
+	"testing"
+
+	"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/config"
+	"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/inspect"
+	"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/parser"
+	sErrors "github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/schema/errors"
+	"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/schema/latest"
+	"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/util/stringslice"
+	"github.com/GoogleContainerTools/skaffold/v2/testutil"
+)
+
+func TestPrintExecutionModesList(t *testing.T) {
+	tests := []struct {
+		description   string
+		profiles      []string
+		module        []string
+		customActions []string
+		err           error
+		expected      string
+	}{
+		{
+			description: "print all executionModes where no executionMode is set in the verify config",
+			expected:    "{\"verifyExecutionModes\":{},\"customActionsExecutionModes\":{}}" + "\n",
+			module:      []string{"cfg-without-executionModes"},
+		},
+		{
+			description: "print all executionModes where one executionMode is set in the verify and customAction config via a profile but no customAction arg",
+			expected:    "{\"verifyExecutionModes\":{\"foo\":\"kubernetesCluster\"},\"customActionsExecutionModes\":{}}" + "\n",
+			profiles:    []string{"has-verify-executionMode"},
+			module:      []string{"cfg-without-executionModes"},
+		},
+		{
+			description: "print all executionModes where one executionMode is set in the verify config via a module",
+			expected:    "{\"verifyExecutionModes\":{\"bar\":\"kubernetesCluster\"},\"customActionsExecutionModes\":{}}" + "\n",
+			module:      []string{"cfg-with-executionModes"},
+		},
+		{
+			description: "actionable error",
+			err:         sErrors.MainConfigFileNotFoundErr("path/to/skaffold.yaml", fmt.Errorf("failed to read file : %q", "skaffold.yaml")),
+			expected:    `{"errorCode":"CONFIG_FILE_NOT_FOUND_ERR","errorMessage":"unable to find configuration file \"path/to/skaffold.yaml\": failed to read file : \"skaffold.yaml\". Check that the specified configuration file exists at \"path/to/skaffold.yaml\"."}` + "\n",
+		},
+		{
+			description: "generic error",
+			err:         errors.New("some error occurred"),
+			expected:    `{"errorCode":"INSPECT_UNKNOWN_ERR","errorMessage":"some error occurred"}` + "\n",
+		},
+	}
+
+	for _, test := range tests {
+		testutil.Run(t, test.description, func(t *testutil.T) {
+			configSet := parser.SkaffoldConfigSet{
+				&parser.SkaffoldConfigEntry{SkaffoldConfig: &latest.SkaffoldConfig{
+					Metadata: latest.Metadata{Name: "cfg-without-executionModes"},
+					Pipeline: latest.Pipeline{},
+					Profiles: []latest.Profile{
+						{Name: "has-verify-executionMode",
+							Pipeline: latest.Pipeline{
+								Verify: []*latest.VerifyTestCase{
+									{
+										Name: "foo",
+										Container: latest.VerifyContainer{
+											Name:  "foo",
+											Image: "foo",
+										},
+										ExecutionMode: latest.VerifyExecutionModeConfig{
+											VerifyExecutionModeType: latest.VerifyExecutionModeType{
+												KubernetesClusterExecutionMode: &latest.KubernetesClusterVerifier{},
+											},
+										},
+									},
+								},
+							},
+						}},
+				}, SourceFile: "path/to/cfg-without-executionModes"},
+
+				&parser.SkaffoldConfigEntry{SkaffoldConfig: &latest.SkaffoldConfig{
+					Metadata: latest.Metadata{Name: "cfg-with-executionModes"},
+					Pipeline: latest.Pipeline{
+						Verify: []*latest.VerifyTestCase{
+							{
+								Name: "bar",
+								Container: latest.VerifyContainer{
+									Name:  "bar",
+									Image: "bar",
+								},
+								ExecutionMode: latest.VerifyExecutionModeConfig{
+									VerifyExecutionModeType: latest.VerifyExecutionModeType{
+										KubernetesClusterExecutionMode: &latest.KubernetesClusterVerifier{},
+									},
+								},
+							},
+						},
+					},
+				}, SourceFile: "path/to/cfg-with-default-namespace"},
+			}
+			t.Override(&inspect.GetConfigSet, func(_ context.Context, opts config.SkaffoldOptions) (parser.SkaffoldConfigSet, error) {
+				// mock profile activation
+				var set parser.SkaffoldConfigSet
+				for _, c := range configSet {
+					if len(opts.ConfigurationFilter) > 0 && !stringslice.Contains(opts.ConfigurationFilter, c.Metadata.Name) {
+						continue
+					}
+					for _, pName := range opts.Profiles {
+						for _, profile := range c.Profiles {
+							if profile.Name != pName {
+								continue
+							}
+							c.Verify = profile.Verify
+							c.CustomActions = profile.CustomActions
+						}
+					}
+					set = append(set, c)
+				}
+				return set, test.err
+			})
+			var buf bytes.Buffer
+			err := PrintExecutionModesList(context.Background(), &buf, inspect.Options{
+				OutFormat: "json", Modules: test.module, Profiles: test.profiles}, test.customActions)
+			t.CheckError(test.err != nil, err)
+			t.CheckDeepEqual(test.expected, buf.String())
+		})
+	}
+}
diff --git a/pkg/skaffold/instrumentation/export_test.go b/pkg/skaffold/instrumentation/export_test.go
index 7b8cff6db..06182a503 100644
--- a/pkg/skaffold/instrumentation/export_test.go
+++ b/pkg/skaffold/instrumentation/export_test.go
@@ -31,7 +31,6 @@ import (
 	sdkmetric "go.opentelemetry.io/otel/sdk/metric"
 
 	"github.com/GoogleContainerTools/skaffold/v2/fs"
-	"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/instrumentation/firelog"
 	"github.com/GoogleContainerTools/skaffold/v2/proto/v1"
 	"github.com/GoogleContainerTools/skaffold/v2/testutil"
 )
@@ -110,6 +109,7 @@ func TestExportMetrics(t *testing.T) {
 	fakeFS := testutil.FakeFileSystem{
 		Files: map[string][]byte{
 			"assets/secrets_generated/keys.json": []byte(testKey),
+			"assets/firelog_generated/key.txt":   []byte("no-empty"),
 		},
 	}
 
@@ -169,7 +169,6 @@ func TestExportMetrics(t *testing.T) {
 
 			fs.AssetsFS = fakeFS
 			t.Override(&isOnline, test.isOnline)
-			t.Override(&firelog.APIKey, "no-empty")
 
 			if test.isOnline {
 				tmpFile, err := os.OpenFile(tmp.Path(openTelFilename), os.O_RDWR|os.O_CREATE, os.ModePerm)
diff --git a/pkg/skaffold/instrumentation/firelog/exporter_test.go b/pkg/skaffold/instrumentation/firelog/exporter_test.go
index 802578b37..5b37bd4ef 100644
--- a/pkg/skaffold/instrumentation/firelog/exporter_test.go
+++ b/pkg/skaffold/instrumentation/firelog/exporter_test.go
@@ -27,29 +27,35 @@ import (
 	"go.opentelemetry.io/otel/attribute"
 	"go.opentelemetry.io/otel/sdk/metric"
 
+	"github.com/GoogleContainerTools/skaffold/v2/fs"
 	"github.com/GoogleContainerTools/skaffold/v2/testutil"
 )
 
 func TestNewFireLogExporter(t *testing.T) {
 	var tests = []struct {
-		name     string
-		expected metric.Exporter
-		apiKey   string
-		wantErr  bool
+		name       string
+		expected   metric.Exporter
+		fileSystem *testutil.FakeFileSystem
+		wantErr    bool
 	}{
 		{
-			name:     "no api key",
+			name: "no api key",
+			fileSystem: &testutil.FakeFileSystem{
+				Files: map[string][]byte{},
+			},
 			expected: nil,
 		},
 		{
 			name:     "has api key",
 			expected: &Exporter{},
-			apiKey:   "test key",
+			fileSystem: &testutil.FakeFileSystem{
+				Files: map[string][]byte{"assets/firelog_generated/key.txt": []byte("test key")},
+			},
 		},
 	}
 	for _, test := range tests {
 		testutil.Run(t, test.name, func(t *testutil.T) {
-			APIKey = test.apiKey
+			fs.AssetsFS = test.fileSystem
 			out, err := NewFireLogExporter()
 			t.CheckError(test.wantErr, err)
 			t.CheckDeepEqual(test.expected, out)
diff --git a/pkg/skaffold/runner/actions_runner_test.go b/pkg/skaffold/runner/actions_runner_test.go
new file mode 100644
index 000000000..1c4f51095
--- /dev/null
+++ b/pkg/skaffold/runner/actions_runner_test.go
@@ -0,0 +1,100 @@
+/*
+Copyright 2023 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 runner
+
+import (
+	"context"
+	"io"
+	"testing"
+
+	"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/actions"
+	"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/actions/docker"
+	"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/deploy/label"
+	dockerutil "github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/docker"
+	"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/graph"
+	"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/runner/runcontext"
+	"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/schema/latest"
+	"github.com/GoogleContainerTools/skaffold/v2/testutil"
+)
+
+type MockActionsRunner struct {
+	ExecEnvByAction map[string]actions.ExecEnv
+	OrderedExecEnvs []actions.ExecEnv
+	AcsByExecEnv    map[actions.ExecEnv][]string
+}
+
+func (mar MockActionsRunner) Exec(ctx context.Context, out io.Writer, allbuilds []graph.Artifact, action string) error {
+	return nil
+}
+
+func (mar MockActionsRunner) ExecAll(ctx context.Context, out io.Writer, allbuilds []graph.Artifact) error {
+	return nil
+}
+
+func TestActionsRunner_Creation(t *testing.T) {
+	tests := []struct {
+		description       string
+		pipelinesCfg      map[string]latest.Pipeline
+		orderedPipelines  []string
+		envFile           string
+		expectedDockerAcs []string
+		inputRunCtx       runcontext.RunContext
+		expectedAr        actions.Runner
+	}{
+		{
+			description: "default to docker when no execution mode is specified",
+			inputRunCtx: runcontext.RunContext{
+				Pipelines: runcontext.NewPipelines(map[string]latest.Pipeline{
+					"config1": {CustomActions: []latest.Action{{Name: "action1"}, {Name: "action2"}}},
+					"config2": {
+						CustomActions: []latest.Action{
+							{
+								Name: "action3",
+							},
+							{
+								Name: "action4",
+								ExecutionModeConfig: latest.ActionExecutionModeConfig{
+									VerifyExecutionModeType: latest.VerifyExecutionModeType{
+										KubernetesClusterExecutionMode: &latest.KubernetesClusterVerifier{},
+									},
+								},
+							},
+						},
+					},
+				}, []string{"config1", "config2"}),
+			},
+			expectedDockerAcs: []string{"action1", "action2", "action3"},
+		},
+	}
+
+	for _, test := range tests {
+		testutil.Run(t, test.description, func(t *testutil.T) {
+			var createdDockerAcs []string
+			t.Override(&docker.NewExecEnv, func(ctx context.Context, cfg dockerutil.Config, labeller *label.DefaultLabeller, resources []*latest.PortForwardResource, network string, envMap map[string]string, acs []latest.Action) (*docker.ExecEnv, error) {
+				for _, a := range acs {
+					createdDockerAcs = append(createdDockerAcs, a.Name)
+				}
+				return &docker.ExecEnv{}, nil
+			})
+
+			_, err := GetActionsRunner(context.TODO(), &test.inputRunCtx, &label.DefaultLabeller{}, "", "")
+
+			t.CheckNoError(err)
+			t.CheckDeepEqual(test.expectedDockerAcs, createdDockerAcs)
+		})
+	}
+}
diff --git a/pkg/skaffold/util/util_test.go b/pkg/skaffold/util/util_test.go
index fac7c25f3..e3b040ebd 100644
--- a/pkg/skaffold/util/util_test.go
+++ b/pkg/skaffold/util/util_test.go
@@ -454,3 +454,32 @@ func TestIsEmptyDir(t *testing.T) {
 	testutil.CheckDeepEqual(t, false, IsEmptyDir(tmpDir.Root()))
 	testutil.CheckDeepEqual(t, false, IsEmptyDir(filepath.Join(tmpDir.Root(), "file")))
 }
+
+func TestParseEnvVariablesFromFile(t *testing.T) {
+	tests := []struct {
+		description string
+		text        string
+		expected    map[string]string
+		shouldErr   bool
+	}{
+		{
+			description: "parsing dotenv file text works and expected map was created",
+			text:        "FOO=my-foo-var",
+			expected:    map[string]string{"FOO": "my-foo-var"},
+			shouldErr:   false,
+		},
+		{
+			description: "parsing dotenv file fails works as file is malformed",
+			text:        "MALFORMED",
+			shouldErr:   true,
+		},
+	}
+	for _, test := range tests {
+		testutil.Run(t, test.description, func(t *testutil.T) {
+			tmpfile := t.TempFile("", []byte(test.text))
+			envMap, err := ParseEnvVariablesFromFile(tmpfile)
+			t.CheckError(test.shouldErr, err)
+			t.CheckDeepEqual(test.expected, envMap)
+		})
+	}
+}
diff --git a/pkg/skaffold/verify/util/util_test.go b/pkg/skaffold/verify/util/util_test.go
deleted file mode 100644
index 68d595bb0..000000000
--- a/pkg/skaffold/verify/util/util_test.go
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
-Copyright 2023 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 util
-
-import (
-	"testing"
-
-	"github.com/GoogleContainerTools/skaffold/v2/testutil"
-)
-
-func TestParseEnvVariablesFromFile(t *testing.T) {
-	tests := []struct {
-		description string
-		text        string
-		expected    map[string]string
-		shouldErr   bool
-	}{
-		{
-			description: "parsing dotenv file text works and expected map was created",
-			text:        "FOO=my-foo-var",
-			expected:    map[string]string{"FOO": "my-foo-var"},
-			shouldErr:   false,
-		},
-		{
-			description: "parsing dotenv file fails works as file is malformed",
-			text:        "MALFORMED",
-			shouldErr:   true,
-		},
-	}
-	for _, test := range tests {
-		testutil.Run(t, test.description, func(t *testutil.T) {
-			tmpfile := t.TempFile("", []byte(test.text))
-			envMap, err := ParseEnvVariablesFromFile(tmpfile)
-			t.CheckError(test.shouldErr, err)
-			t.CheckDeepEqual(test.expected, envMap)
-		})
-	}
-}