Repository: linkerd/linkerd2
Pull request #6791 (2021-09-01 17:02:08)

build(deps-dev): bump webpack-dev-server from 4.0.0 to 4.1.0 in /web/app
Bumps [webpack-dev-server](https://github.com/webpack/webpack-dev-server) from 4.0.0 to 4.1.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a href="https://github.com/webpack/webpack-dev-server/releases">webpack-dev-server's releases</a>.</em></p>
<blockquote>
<h2>v4.1.0</h2>
<h2><a href="https://github.com/webpack/webpack-dev-server/compare/v4.0.0...v4.1.0">4.1.0</a> (2021-08-31)</h2>
<h3>Features</h3>
<ul>
<li>added the <code>magicHtml</code> option (<a href="https://github-redirect.dependabot.com/webpack/webpack-dev-server/issues/3717">#3717</a>) (<a href="https://github.com/webpack/webpack-dev-server/commit/4831f58a75d12f75f4d5e5064ff3245f1a3770ba">4831f58</a>)</li>
<li>allow to set <code>hot</code> and <code>live-reload</code> for client using search params (<a href="https://github.com/webpack/webpack-dev-server/commit/1c576802bff4a4be78b1771ed2fc8f343780663b">1c57680</a>)</li>
<li>show warning when the <code>hot</code> option is enabled with the HMR plugin in config (<a href="https://github-redirect.dependabot.com/webpack/webpack-dev-server/issues/3744">#3744</a>) (<a href="https://github.com/webpack/webpack-dev-server/commit/6cb1e4e6a25de3fb284be84d2ad67fc701800b96">6cb1e4e</a>)</li>
</ul>
<h3>Bug Fixes</h3>
<ul>
<li>change log type of <code>Disconnected!</code> to <code>info</code> (<a href="https://github.com/webpack/webpack-dev-server/commit/fde27f5002ae3d679b2cee2215287f6e3a16febf">fde27f5</a>)</li>
<li>handle <code>--allowed-hosts all</code> correctly (<a href="https://github-redirect.dependabot.com/webpack/webpack-dev-server/issues/3720">#3720</a>) (<a href="https://github.com/webpack/webpack-dev-server/commit/326ed567638944bc3b64e185b118f9e723383e44">326ed56</a>)</li>
<li>output documentation link on errors (<a href="https://github-redirect.dependabot.com/webpack/webpack-dev-server/issues/3680">#3680</a>) (<a href="https://github.com/webpack/webpack-dev-server/commit/e16221b1aed48f93c31c8659438834fdbf1e62bf">e16221b</a>)</li>
<li>respect the <code>bypass</code> option with <code>target</code>/<code>router</code> options for proxy (<a href="https://github.com/webpack/webpack-dev-server/commit/b5dd5687e521cd70e7e13b397280d8bda38df255">b5dd568</a>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a href="https://github.com/webpack/webpack-dev-server/blob/master/CHANGELOG.md">webpack-dev-server's changelog</a>.</em></p>
<blockquote>
<h2><a href="https://github.com/webpack/webpack-dev-server/compare/v4.0.0...v4.1.0">4.1.0</a> (2021-08-31)</h2>
<h3>Features</h3>
<ul>
<li>added the <code>magicHtml</code> option (<a href="https://github-redirect.dependabot.com/webpack/webpack-dev-server/issues/3717">#3717</a>) (<a href="https://github.com/webpack/webpack-dev-server/commit/4831f58a75d12f75f4d5e5064ff3245f1a3770ba">4831f58</a>)</li>
<li>allow to set <code>hot</code> and <code>live-reload</code> for client using search params (<a href="https://github.com/webpack/webpack-dev-server/commit/1c576802bff4a4be78b1771ed2fc8f343780663b">1c57680</a>)</li>
<li>show warning when the <code>hot</code> option is enabled with the HMR plugin in config (<a href="https://github-redirect.dependabot.com/webpack/webpack-dev-server/issues/3744">#3744</a>) (<a href="https://github.com/webpack/webpack-dev-server/commit/6cb1e4e6a25de3fb284be84d2ad67fc701800b96">6cb1e4e</a>)</li>
</ul>
<h3>Bug Fixes</h3>
<ul>
<li>change log type of <code>Disconnected!</code> to <code>info</code> (<a href="https://github.com/webpack/webpack-dev-server/commit/fde27f5002ae3d679b2cee2215287f6e3a16febf">fde27f5</a>)</li>
<li>handle <code>--allowed-hosts all</code> correctly (<a href="https://github-redirect.dependabot.com/webpack/webpack-dev-server/issues/3720">#3720</a>) (<a href="https://github.com/webpack/webpack-dev-server/commit/326ed567638944bc3b64e185b118f9e723383e44">326ed56</a>)</li>
<li>output documentation link on errors (<a href="https://github-redirect.dependabot.com/webpack/webpack-dev-server/issues/3680">#3680</a>) (<a href="https://github.com/webpack/webpack-dev-server/commit/e16221b1aed48f93c31c8659438834fdbf1e62bf">e16221b</a>)</li>
<li>respect the <code>bypass</code> option with <code>target</code>/<code>router</code> options for proxy (<a href="https://github.com/webpack/webpack-dev-server/commit/b5dd5687e521cd70e7e13b397280d8bda38df255">b5dd568</a>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a href="https://github.com/webpack/webpack-dev-server/commit/dfca34ed441e9e253839f2fe953463edf3f95a42"><code>dfca34e</code></a> chore(release): 4.1.0</li>
<li><a href="https://github.com/webpack/webpack-dev-server/commit/056f402f08af58c08d289a71f62b9d629576175c"><code>056f402</code></a> chore(deps-dev): bump jest from 27.0.6 to 27.1.0 (<a href="https://github-redirect.dependabot.com/webpack/webpack-dev-server/issues/3764">#3764</a>)</li>
<li><a href="https://github.com/webpack/webpack-dev-server/commit/d4282dc2f6858b2768658fbc091660bdd704cb05"><code>d4282dc</code></a> refactor(Server): remove redundant anonymous functions (<a href="https://github-redirect.dependabot.com/webpack/webpack-dev-server/issues/3750">#3750</a>)</li>
<li><a href="https://github.com/webpack/webpack-dev-server/commit/fde27f5002ae3d679b2cee2215287f6e3a16febf"><code>fde27f5</code></a> fix: change log type of &quot;Disconnected!&quot; to info</li>
<li><a href="https://github.com/webpack/webpack-dev-server/commit/0b8ebd6a3ae751d784a63568b4cd8d5dffb63878"><code>0b8ebd6</code></a> chore(deps-dev): bump core-js</li>
<li><a href="https://github.com/webpack/webpack-dev-server/commit/fe578bb38a1facfdc3d2d847471025ef0710b057"><code>fe578bb</code></a> chore(deps-dev): bump babel-jest</li>
<li><a href="https://github.com/webpack/webpack-dev-server/commit/e8db020ada7b3ade2ea083eb0e90c11af0839f4a"><code>e8db020</code></a> chore(deps-dev): bump typescript from 4.3.5 to 4.4.2 (<a href="https://github-redirect.dependabot.com/webpack/webpack-dev-server/issues/3756">#3756</a>)</li>
<li><a href="https://github.com/webpack/webpack-dev-server/commit/c846a4573cf77418b09abaf5db30b1d23aaa0297"><code>c846a45</code></a> chore(deps): bump ws from 8.2.0 to 8.2.1 (<a href="https://github-redirect.dependabot.com/webpack/webpack-dev-server/issues/3766">#3766</a>)</li>
<li><a href="https://github.com/webpack/webpack-dev-server/commit/3b30acbb3e11d83f6911f4927e2e7efdf0572e24"><code>3b30acb</code></a> docs: use more specific text in <code>historyApiFallback</code> example (<a href="https://github-redirect.dependabot.com/webpack/webpack-dev-server/issues/3767">#3767</a>)</li>
<li><a href="https://github.com/webpack/webpack-dev-server/commit/caa336270c63e4846b7f9b0304a5621e5aaa253c"><code>caa3362</code></a> test: refactor e2e tests to use <code>response.text()</code></li>
<li>Additional commits viewable in <a href="https://github.com/webpack/webpack-dev-server/compare/v4.0.0...v4.1.0">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=webpack-dev-server&package-manager=npm_and_yarn&previous-version=4.0.0&new-version=4.1.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)


</details>


Patch:
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index cb5ec6e9b..089de9664 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -7,6 +7,7 @@ permissions:
   contents: read
 env:
   GH_ANNOTATION: true
+  DOCKER_REGISTRY: cr.l5d.io/linkerd
 jobs:
   docker_build:
     runs-on: ubuntu-20.04
@@ -23,9 +24,6 @@ jobs:
       run: |
         . bin/_tag.sh
         echo "TAG=$(CI_FORCE_CLEAN=1 bin/root-tag)" >> $GITHUB_ENV
-
-        . bin/_docker.sh
-        echo "DOCKER_REGISTRY=ghcr.io/linkerd" >> $GITHUB_ENV
         echo "DOCKER_BUILDKIT_CACHE=${{ runner.temp }}/.buildx-cache" >> $GITHUB_ENV
     - name: Cache docker layers
       uses: actions/cache@c64c572235d810460d0d6876e9c705ad5002b353
@@ -90,7 +88,7 @@ jobs:
         echo "${{ secrets.DOCKER_GHCR_PAT }}" | docker login ghcr.io -u "${{ secrets.DOCKER_GHCR_USERNAME }}" --password-stdin
         docker buildx build --push ./policy-controller/ \
           -f ./policy-controller/${{ matrix.arch }}.dockerfile \
-          -t ghcr.io/linkerd/policy-controller:$TAG-${{ matrix.arch }}
+          -t $DOCKER_REGISTRY/policy-controller:$TAG-${{ matrix.arch }}
 
   policy_controller_manifest:
     runs-on: ubuntu-20.04
@@ -105,22 +103,22 @@ jobs:
         echo "TAG=$(CI_FORCE_CLEAN=1 bin/root-tag)" >> $GITHUB_ENV
     - name: Create multiarch manifest
       run: |
-        docker manifest create ghcr.io/linkerd/policy-controller:${TAG} \
-            ghcr.io/linkerd/policy-controller:${TAG}-amd64 \
-            ghcr.io/linkerd/policy-controller:${TAG}-arm64 \
-            ghcr.io/linkerd/policy-controller:${TAG}-arm
+        docker manifest create $DOCKER_REGISTRY/policy-controller:$TAG \
+            $DOCKER_REGISTRY/policy-controller:$TAG-amd64 \
+            $DOCKER_REGISTRY/policy-controller:$TAG-arm64 \
+            $DOCKER_REGISTRY/policy-controller:$TAG-arm
     - name: Annotate multiarch manifest
       run: |
-        docker manifest annotate ghcr.io/linkerd/policy-controller:$TAG \
-            ghcr.io/linkerd/policy-controller:${TAG}-amd64 --os=linux --arch=amd64
-        docker manifest annotate ghcr.io/linkerd/policy-controller:$TAG \
-            ghcr.io/linkerd/policy-controller:${TAG}-arm64 --os=linux --arch=arm64
-        docker manifest annotate ghcr.io/linkerd/policy-controller:$TAG \
-            ghcr.io/linkerd/policy-controller:${TAG}-arm --os=linux --arch=arm
+        docker manifest annotate $DOCKER_REGISTRY/policy-controller:$TAG \
+            $DOCKER_REGISTRY/policy-controller:${TAG}-amd64 --os=linux --arch=amd64
+        docker manifest annotate $DOCKER_REGISTRY/policy-controller:$TAG \
+            $DOCKER_REGISTRY/policy-controller:${TAG}-arm64 --os=linux --arch=arm64
+        docker manifest annotate $DOCKER_REGISTRY/policy-controller:$TAG \
+            $DOCKER_REGISTRY/policy-controller:${TAG}-arm --os=linux --arch=arm
     - name: Push multiarch manifest
       run: |
         echo "${{ secrets.DOCKER_GHCR_PAT }}" | docker login ghcr.io -u "${{ secrets.DOCKER_GHCR_USERNAME }}" --password-stdin
-        docker manifest push ghcr.io/linkerd/policy-controller:$TAG
+        docker manifest push $DOCKER_REGISTRY/policy-controller:$TAG
 
   # todo: Keep in sync with `integration_tests.yml`
   windows_static_cli_tests:
@@ -180,6 +178,8 @@ jobs:
         echo "CMD=$CMD" >> $GITHUB_ENV
         echo "TAG=$TAG" >> $GITHUB_ENV
     - name: Run integration tests
+      env:
+        LINKERD_DOCKER_REGISTRY: ${{ env.DOCKER_REGISTRY }}
       run: |
         bin/docker-pull-binaries $TAG
         # Validate the CLI version matches the current build tag.
@@ -220,6 +220,7 @@ jobs:
     - name: Run integration tests
       env:
         RUN_ARM_TEST: 1
+        LINKERD_DOCKER_REGISTRY: ${{ env.DOCKER_REGISTRY }}
       run: bin/tests --name deep --images preload --skip-cluster-create "$CMD"
     - name: CNI tests
       run: |
diff --git a/cli/cmd/install-cni-plugin.go b/cli/cmd/install-cni-plugin.go
index 51d3811c2..16980e854 100644
--- a/cli/cmd/install-cni-plugin.go
+++ b/cli/cmd/install-cni-plugin.go
@@ -9,6 +9,8 @@ import (
 	"github.com/linkerd/linkerd2/pkg/charts"
 	cnicharts "github.com/linkerd/linkerd2/pkg/charts/cni"
 	"github.com/linkerd/linkerd2/pkg/charts/static"
+	"github.com/linkerd/linkerd2/pkg/cmd"
+	"github.com/linkerd/linkerd2/pkg/flags"
 	"github.com/linkerd/linkerd2/pkg/version"
 	log "github.com/sirupsen/logrus"
 	"github.com/spf13/cobra"
@@ -66,8 +68,12 @@ func (options *cniPluginOptions) validate() error {
 }
 
 func (options *cniPluginOptions) pluginImage() string {
+	// env var overrides CLI flag
+	if override := os.Getenv(flags.EnvOverrideDockerRegistry); override != "" {
+		return cmd.RegistryOverride(options.cniPluginImage, override)
+	}
 	if options.dockerRegistry != defaultDockerRegistry {
-		return registryOverride(options.cniPluginImage, options.dockerRegistry)
+		return cmd.RegistryOverride(options.cniPluginImage, options.dockerRegistry)
 	}
 	return options.cniPluginImage
 }
@@ -95,7 +101,8 @@ assumes that the 'linkerd install' command will be executed with the
 	}
 
 	cmd.PersistentFlags().StringVarP(&options.linkerdVersion, "linkerd-version", "v", options.linkerdVersion, "Tag to be used for Linkerd images")
-	cmd.PersistentFlags().StringVar(&options.dockerRegistry, "registry", options.dockerRegistry, "Docker registry to pull images from")
+	cmd.PersistentFlags().StringVar(&options.dockerRegistry, "registry", options.dockerRegistry,
+		fmt.Sprintf("Docker registry to pull images from ($%s)", flags.EnvOverrideDockerRegistry))
 	cmd.PersistentFlags().Int64Var(&options.proxyUID, "proxy-uid", options.proxyUID, "Run the proxy under this user ID")
 	cmd.PersistentFlags().UintVar(&options.inboundPort, "inbound-port", options.inboundPort, "Proxy port to use for inbound traffic")
 	cmd.PersistentFlags().UintVar(&options.outboundPort, "outbound-port", options.outboundPort, "Proxy port to use for outbound traffic")
diff --git a/cli/cmd/options.go b/cli/cmd/options.go
index 6c2b0d600..24c383e1a 100644
--- a/cli/cmd/options.go
+++ b/cli/cmd/options.go
@@ -3,6 +3,7 @@ package cmd
 import (
 	"context"
 	"errors"
+	"os"
 
 	"fmt"
 	"io/ioutil"
@@ -12,6 +13,8 @@ import (
 
 	"github.com/linkerd/linkerd2/cli/flag"
 	l5dcharts "github.com/linkerd/linkerd2/pkg/charts/linkerd2"
+	"github.com/linkerd/linkerd2/pkg/cmd"
+	flagspkg "github.com/linkerd/linkerd2/pkg/flags"
 	"github.com/linkerd/linkerd2/pkg/inject"
 	"github.com/linkerd/linkerd2/pkg/issuercerts"
 	"github.com/linkerd/linkerd2/pkg/k8s"
@@ -305,15 +308,6 @@ func makeProxyFlags(defaults *l5dcharts.Values) ([]flag.Flag, *pflag.FlagSet) {
 				return nil
 			}),
 
-		flag.NewStringFlag(proxyFlags, "registry", defaultDockerRegistry, "Docker registry to pull images from",
-			func(values *l5dcharts.Values, value string) error {
-				values.ControllerImage = registryOverride(values.ControllerImage, value)
-				values.DebugContainer.Image.Name = registryOverride(values.DebugContainer.Image.Name, value)
-				values.Proxy.Image.Name = registryOverride(values.Proxy.Image.Name, value)
-				values.ProxyInit.Image.Name = registryOverride(values.ProxyInit.Image.Name, value)
-				return nil
-			}),
-
 		flag.NewStringFlag(proxyFlags, "image-pull-policy", defaults.ImagePullPolicy,
 			"Docker image pull policy", func(values *l5dcharts.Values, value string) error {
 				values.ImagePullPolicy = value
@@ -425,6 +419,21 @@ func makeProxyFlags(defaults *l5dcharts.Values) ([]flag.Flag, *pflag.FlagSet) {
 			}),
 	}
 
+	registryFlag := flag.NewStringFlag(proxyFlags, "registry", defaultDockerRegistry,
+		fmt.Sprintf("Docker registry to pull images from ($%s)", flagspkg.EnvOverrideDockerRegistry),
+		func(values *l5dcharts.Values, value string) error {
+			values.ControllerImage = cmd.RegistryOverride(values.ControllerImage, value)
+			values.PolicyController.Image.Name = cmd.RegistryOverride(values.PolicyController.Image.Name, value)
+			values.DebugContainer.Image.Name = cmd.RegistryOverride(values.DebugContainer.Image.Name, value)
+			values.Proxy.Image.Name = cmd.RegistryOverride(values.Proxy.Image.Name, value)
+			values.ProxyInit.Image.Name = cmd.RegistryOverride(values.ProxyInit.Image.Name, value)
+			return nil
+		})
+	if reg := os.Getenv(flagspkg.EnvOverrideDockerRegistry); reg != "" {
+		registryFlag.Set(reg)
+	}
+	flags = append(flags, registryFlag)
+
 	proxyFlags.MarkDeprecated("proxy-memory", "use --proxy-memory-request instead")
 	proxyFlags.MarkDeprecated("proxy-cpu", "use --proxy-cpu-request instead")
 
diff --git a/cli/cmd/root.go b/cli/cmd/root.go
index 05f834170..27a8f704e 100644
--- a/cli/cmd/root.go
+++ b/cli/cmd/root.go
@@ -11,6 +11,7 @@ import (
 	jaeger "github.com/linkerd/linkerd2/jaeger/cmd"
 	multicluster "github.com/linkerd/linkerd2/multicluster/cmd"
 	pkgcmd "github.com/linkerd/linkerd2/pkg/cmd"
+	"github.com/linkerd/linkerd2/pkg/flags"
 	"github.com/linkerd/linkerd2/pkg/healthcheck"
 	viz "github.com/linkerd/linkerd2/viz/cmd"
 	log "github.com/sirupsen/logrus"
@@ -79,7 +80,7 @@ var RootCmd = &cobra.Command{
 			log.SetLevel(log.PanicLevel)
 		}
 
-		controlPlaneNamespaceFromEnv := os.Getenv("LINKERD_NAMESPACE")
+		controlPlaneNamespaceFromEnv := os.Getenv(flags.EnvOverrideNamespace)
 		if controlPlaneNamespace == defaultLinkerdNamespace && controlPlaneNamespaceFromEnv != "" {
 			controlPlaneNamespace = controlPlaneNamespaceFromEnv
 		}
@@ -93,7 +94,9 @@ var RootCmd = &cobra.Command{
 }
 
 func init() {
-	RootCmd.PersistentFlags().StringVarP(&controlPlaneNamespace, "linkerd-namespace", "L", defaultLinkerdNamespace, "Namespace in which Linkerd is installed ($LINKERD_NAMESPACE)")
+	RootCmd.PersistentFlags().StringVarP(&controlPlaneNamespace, "linkerd-namespace", "L",
+		defaultLinkerdNamespace,
+		fmt.Sprintf("Namespace in which Linkerd is installed ($%s)", flags.EnvOverrideNamespace))
 	RootCmd.PersistentFlags().StringVarP(&cniNamespace, "cni-namespace", "", defaultCNINamespace, "Namespace in which the Linkerd CNI plugin is installed")
 	RootCmd.PersistentFlags().StringVar(&kubeconfigPath, "kubeconfig", "", "Path to the kubeconfig file to use for CLI requests")
 	RootCmd.PersistentFlags().StringVar(&kubeContext, "context", "", "Name of the kubeconfig context to use")
@@ -143,22 +146,6 @@ func deprecateCmd(cmd *cobra.Command) *cobra.Command {
 	return cmd
 }
 
-// registryOverride replaces the registry-portion of the provided image with the provided registry.
-func registryOverride(image, newRegistry string) string {
-	if image == "" {
-		return image
-	}
-	registry := newRegistry
-	if registry != "" && !strings.HasSuffix(registry, slash) {
-		registry += slash
-	}
-	imageName := image
-	if strings.Contains(image, slash) {
-		imageName = image[strings.LastIndex(image, slash)+1:]
-	}
-	return registry + imageName
-}
-
 func flattenFlags(flags ...[]flag.Flag) []flag.Flag {
 	out := []flag.Flag{}
 	for _, f := range flags {
diff --git a/cli/flag/flag.go b/cli/flag/flag.go
index 46311df55..c44bb1fe4 100644
--- a/cli/flag/flag.go
+++ b/cli/flag/flag.go
@@ -186,6 +186,11 @@ func (flag *StringFlag) Apply(values *charts.Values) error {
 	return flag.apply(values, flag.Value)
 }
 
+// Set sets the given value to the underlying Flag
+func (flag *StringFlag) Set(value string) error {
+	return flag.flagSet.Set(flag.name, value)
+}
+
 // IsSet returns true if and only if the Flag has been explicitly set with a value.
 func (flag *StringFlag) IsSet() bool {
 	return flag.flagSet.Changed(flag.name)
diff --git a/jaeger/cmd/install.go b/jaeger/cmd/install.go
index 231d4ddb2..6fd228fe5 100644
--- a/jaeger/cmd/install.go
+++ b/jaeger/cmd/install.go
@@ -2,6 +2,7 @@ package cmd
 
 import (
 	"bytes"
+	"fmt"
 	"io"
 	"os"
 	"path"
@@ -10,6 +11,7 @@ import (
 	"github.com/linkerd/linkerd2/jaeger/static"
 	"github.com/linkerd/linkerd2/pkg/charts"
 	partials "github.com/linkerd/linkerd2/pkg/charts/static"
+	"github.com/linkerd/linkerd2/pkg/cmd"
 	"github.com/linkerd/linkerd2/pkg/flags"
 	"github.com/linkerd/linkerd2/pkg/healthcheck"
 	api "github.com/linkerd/linkerd2/pkg/public"
@@ -31,6 +33,7 @@ var (
 )
 
 func newCmdInstall() *cobra.Command {
+	var registry string
 	var skipChecks bool
 	var wait time.Duration
 	var options values.Options
@@ -62,10 +65,12 @@ A full list of configurable values can be found at https://www.github.com/linker
 				})
 			}
 
-			return install(os.Stdout, options)
+			return install(os.Stdout, options, registry)
 		},
 	}
 
+	cmd.Flags().StringVar(&registry, "registry", "cr.l5d.io/linkerd",
+		fmt.Sprintf("Docker registry to pull jaeger-webhook image from ($%s)", flags.EnvOverrideDockerRegistry))
 	cmd.Flags().BoolVar(&skipChecks, "skip-checks", false, `Skip checks for linkerd core control-plane existence`)
 	cmd.Flags().DurationVar(&wait, "wait", 300*time.Second, "Wait for core control-plane components to be available")
 
@@ -74,7 +79,7 @@ A full list of configurable values can be found at https://www.github.com/linker
 	return cmd
 }
 
-func install(w io.Writer, options values.Options) error {
+func install(w io.Writer, options values.Options, registry string) error {
 
 	// Create values override
 	valuesOverrides, err := options.MergeValues(nil)
@@ -84,10 +89,10 @@ func install(w io.Writer, options values.Options) error {
 
 	// TODO: Add any validation logic here
 
-	return render(w, valuesOverrides)
+	return render(w, valuesOverrides, registry)
 }
 
-func render(w io.Writer, valuesOverrides map[string]interface{}) error {
+func render(w io.Writer, valuesOverrides map[string]interface{}, registry string) error {
 
 	files := []*loader.BufferedFile{
 		{Name: chartutil.ChartfileName},
@@ -133,6 +138,15 @@ func render(w io.Writer, valuesOverrides map[string]interface{}) error {
 		return err
 	}
 
+	regOrig := vals["webhook"].(map[string]interface{})["image"].(map[string]interface{})["name"].(string)
+	if registry != "" {
+		vals["webhook"].(map[string]interface{})["image"].(map[string]interface{})["name"] = cmd.RegistryOverride(regOrig, registry)
+	}
+	// env var overrides CLI flag
+	if override := os.Getenv(flags.EnvOverrideDockerRegistry); override != "" {
+		vals["webhook"].(map[string]interface{})["image"].(map[string]interface{})["name"] = cmd.RegistryOverride(regOrig, override)
+	}
+
 	// Attach the final values into the `Values` field for rendering to work
 	renderedTemplates, err := engine.Render(chart, map[string]interface{}{"Values": vals})
 	if err != nil {
diff --git a/multicluster/cmd/link.go b/multicluster/cmd/link.go
index 55edae876..6c472e39e 100644
--- a/multicluster/cmd/link.go
+++ b/multicluster/cmd/link.go
@@ -327,7 +327,8 @@ A full list of configurable values can be found at https://github.com/linkerd/li
 	cmd.Flags().StringVar(&opts.gatewayNamespace, "gateway-namespace", defaultMulticlusterNamespace, "The namespace of the gateway service")
 	cmd.Flags().Uint32Var(&opts.serviceMirrorRetryLimit, "service-mirror-retry-limit", opts.serviceMirrorRetryLimit, "The number of times a failed update from the target cluster is allowed to be retried")
 	cmd.Flags().StringVar(&opts.logLevel, "log-level", opts.logLevel, "Log level for the Multicluster components")
-	cmd.Flags().StringVar(&opts.dockerRegistry, "registry", opts.dockerRegistry, "Docker registry to pull service mirror controller image from")
+	cmd.Flags().StringVar(&opts.dockerRegistry, "registry", opts.dockerRegistry,
+		fmt.Sprintf("Docker registry to pull service mirror controller image from ($%s)", flags.EnvOverrideDockerRegistry))
 	cmd.Flags().StringVarP(&opts.selector, "selector", "l", opts.selector, "Selector (label query) to filter which services in the target cluster to mirror")
 	cmd.Flags().StringVar(&opts.gatewayAddresses, "gateway-addresses", opts.gatewayAddresses, "If specified, overwrites gateway addresses when gateway service is not type LoadBalancer (comma separated list)")
 	cmd.Flags().Uint32Var(&opts.gatewayPort, "gateway-port", opts.gatewayPort, "If specified, overwrites gateway port when gateway service is not type LoadBalancer")
@@ -379,6 +380,10 @@ func buildServiceMirrorValues(opts *linkOptions) (*multicluster.Values, error) {
 		return nil, err
 	}
 
+	if reg := os.Getenv(flags.EnvOverrideDockerRegistry); reg != "" {
+		opts.dockerRegistry = reg
+	}
+
 	defaults.TargetClusterName = opts.clusterName
 	defaults.Namespace = opts.namespace
 	defaults.ServiceMirrorRetryLimit = opts.serviceMirrorRetryLimit
diff --git a/pkg/cmd/cmd.go b/pkg/cmd/cmd.go
index 8f1e32d1c..2b0808298 100644
--- a/pkg/cmd/cmd.go
+++ b/pkg/cmd/cmd.go
@@ -146,3 +146,19 @@ func GetLabelSelector(labelKey string, labelValues ...string) (string, error) {
 	selector := labels.NewSelector().Add(*labelRequirement)
 	return selector.String(), nil
 }
+
+// RegistryOverride replaces the registry-portion of the provided image with the provided registry.
+func RegistryOverride(image, newRegistry string) string {
+	if image == "" {
+		return image
+	}
+	registry := newRegistry
+	if registry != "" && !strings.HasSuffix(registry, "/") {
+		registry += "/"
+	}
+	imageName := image
+	if strings.Contains(image, "/") {
+		imageName = image[strings.LastIndex(image, "/")+1:]
+	}
+	return registry + imageName
+}
diff --git a/pkg/flags/flags.go b/pkg/flags/flags.go
index 82ca13b0b..71f66d736 100644
--- a/pkg/flags/flags.go
+++ b/pkg/flags/flags.go
@@ -12,6 +12,18 @@ import (
 	klog "k8s.io/klog/v2"
 )
 
+const (
+
+	// EnvOverrideNamespace is the environment variable used in the CLI to
+	// overidde the control-plane's namespace
+	EnvOverrideNamespace = "LINKERD_NAMESPACE"
+
+	// EnvOverrideDockerRegistry is the environment variable used in the
+	// CLI to override the docker images' registry in the control-plane
+	// manifests
+	EnvOverrideDockerRegistry = "LINKERD_DOCKER_REGISTRY"
+)
+
 // ConfigureAndParse adds flags that are common to all go processes. This
 // func calls flag.Parse(), so it should be called after all other flags have
 // been configured.
diff --git a/viz/cmd/install.go b/viz/cmd/install.go
index 82d78f70a..ea67f9f0f 100644
--- a/viz/cmd/install.go
+++ b/viz/cmd/install.go
@@ -92,11 +92,13 @@ func install(w io.Writer, options values.Options, ha bool) error {
 		return err
 	}
 
-	// if using -L to specify a non-standard CP namespace, make sure
-	// the linkerdNamespace Helm value is synced
+	// sync values overrides with Helm values
 	if controlPlaneNamespace != defaultLinkerdNamespace {
 		valuesOverrides["linkerdNamespace"] = controlPlaneNamespace
 	}
+	if reg := os.Getenv(flags.EnvOverrideDockerRegistry); reg != "" {
+		valuesOverrides["defaultRegistry"] = reg
+	}
 
 	if ha {
 		valuesOverrides, err = charts.OverrideFromFile(valuesOverrides, static.Templates, vizChartName, "values-ha.yaml")
diff --git a/web/app/package.json b/web/app/package.json
index c21ec7827..ba1ff135f 100644
--- a/web/app/package.json
+++ b/web/app/package.json
@@ -81,7 +81,7 @@
     "webpack": "^5.51.1",
     "webpack-bundle-analyzer": "4.4.2",
     "webpack-cli": "4.8.0",
-    "webpack-dev-server": "4.0.0"
+    "webpack-dev-server": "4.1.0"
   },
   "resolutions": {
     "@lingui/**/**/minimist": ">=1.2.5",
diff --git a/web/app/yarn.lock b/web/app/yarn.lock
index eb1f45309..b8ca8d83a 100644
--- a/web/app/yarn.lock
+++ b/web/app/yarn.lock
@@ -9511,10 +9511,10 @@ webpack-dev-middleware@^5.0.0:
     range-parser "^1.2.1"
     schema-utils "^3.0.0"
 
-webpack-dev-server@4.0.0:
-  version "4.0.0"
-  resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-4.0.0.tgz#fb4906e91182154bba54a66e6e06f84c1e3c0a80"
-  integrity sha512-ya5cjoBSf3LqrshZn2HMaRZQx8YRNBE+tx+CQNFGaLLHrvs4Y1aik0sl5SFhLz2cW1O9/NtyaZhthc+8UiuvkQ==
+webpack-dev-server@4.1.0:
+  version "4.1.0"
+  resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-4.1.0.tgz#1c7a7b77bdafec334a8b81d26c586f793001b5ca"
+  integrity sha512-PnnoCHuLKxH3vYff2EbORntD0Pd+MKclDIO8AcKsDVRToqY9/oeIwwUs5alA2B5OPgXJhaDNkBJAmb0OaWZFJA==
   dependencies:
     ansi-html "^0.0.7"
     bonjour "^3.5.0"


Test patch:
diff --git a/.github/workflows/integration_tests.yml b/.github/workflows/integration_tests.yml
index e173604eb..231a53bae 100644
--- a/.github/workflows/integration_tests.yml
+++ b/.github/workflows/integration_tests.yml
@@ -12,6 +12,7 @@ permissions:
   contents: read
 env:
   GH_ANNOTATION: true
+  DOCKER_REGISTRY: ghcr.io/linkerd
 jobs:
   docker_build:
     runs-on: ubuntu-20.04
@@ -28,9 +29,6 @@ jobs:
       run: |
         . bin/_tag.sh
         echo "TAG=$(CI_FORCE_CLEAN=1 bin/root-tag)" >> $GITHUB_ENV
-
-        . bin/_docker.sh
-        echo "DOCKER_REGISTRY=cr.l5d.io/linkerd" >> $GITHUB_ENV
         echo "DOCKER_BUILDKIT_CACHE=${{ runner.temp }}/.buildx-cache" >> $GITHUB_ENV
     - name: Cache docker layers
       uses: actions/cache@c64c572235d810460d0d6876e9c705ad5002b353
@@ -54,7 +52,7 @@ jobs:
         ARCHIVES: /home/runner/archives
       run: |
         mkdir -p $ARCHIVES
-        docker save "cr.l5d.io/linkerd/${{ matrix.target }}:$TAG" > $ARCHIVES/${{ matrix.target }}.tar
+        docker save "$DOCKER_REGISTRY/${{ matrix.target }}:$TAG" > $ARCHIVES/${{ matrix.target }}.tar
     # `with.path` values do not support environment variables yet, so an
     # absolute path is used here.
     #
@@ -99,9 +97,6 @@ jobs:
       run: |
         . bin/_tag.sh
         echo "TAG=$(CI_FORCE_CLEAN=1 bin/root-tag)" >> $GITHUB_ENV
-
-        . bin/_docker.sh
-        echo "DOCKER_REGISTRY=cr.l5d.io/linkerd" >> $GITHUB_ENV
     - name: Download image archives
       uses: actions/download-artifact@3be87be14a055c47b01d3bd88f8fe02320a9bb60
       with:
@@ -112,11 +107,13 @@ jobs:
     - name: Install CLI
       run: |
         # Copy the CLI out of the local cli-bin container.
-        container_id=$(docker create "cr.l5d.io/linkerd/cli-bin:$TAG")
+        container_id=$(docker create "$DOCKER_REGISTRY/cli-bin:$TAG")
         docker cp $container_id:/out/linkerd-linux-amd64 "$HOME/.linkerd"
 
         # Validate the CLI version matches the current build tag.
         [[ "$TAG" == "$($HOME/.linkerd version --short --client)" ]]
     - name: Run integration tests
+      env:
+        LINKERD_DOCKER_REGISTRY: ${{ env.DOCKER_REGISTRY }}
       run: |
         bin/tests --images archive --cleanup-docker --name ${{ matrix.integration_test }} "$HOME/.linkerd"
diff --git a/cli/cmd/inject_test.go b/cli/cmd/inject_test.go
index 12c1ca104..772ec5af7 100644
--- a/cli/cmd/inject_test.go
+++ b/cli/cmd/inject_test.go
@@ -12,6 +12,7 @@ import (
 	"testing"
 
 	"github.com/linkerd/linkerd2/pkg/charts/linkerd2"
+	"github.com/linkerd/linkerd2/pkg/cmd"
 	"github.com/linkerd/linkerd2/pkg/k8s"
 	"github.com/linkerd/linkerd2/testutil"
 )
@@ -760,7 +761,7 @@ func TestOverwriteRegistry(t *testing.T) {
 	for i, tc := range testCases {
 		tc := tc // pin
 		t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
-			actual := registryOverride(tc.image, tc.registry)
+			actual := cmd.RegistryOverride(tc.image, tc.registry)
 			if actual != tc.expected {
 				t.Fatalf("expected %q, but got %q", tc.expected, actual)
 			}
diff --git a/cli/cmd/testdata/install_custom_registry.golden b/cli/cmd/testdata/install_custom_registry.golden
index 5bf8c2ee8..f2eaa398e 100644
--- a/cli/cmd/testdata/install_custom_registry.golden
+++ b/cli/cmd/testdata/install_custom_registry.golden
@@ -1099,7 +1099,7 @@ data:
     policyController:
       defaultAllowPolicy: all-unauthenticated
       image:
-        name: cr.l5d.io/linkerd/policy-controller
+        name: my.custom.registry/linkerd-io/policy-controller
         pullPolicy: ""
         version: install-control-plane-version
       logLevel: linkerd=info,warn
@@ -1832,7 +1832,7 @@ spec:
         env:
         - name: RUST_LOG
           value: "linkerd=info,warn"
-        image: cr.l5d.io/linkerd/policy-controller:install-control-plane-version
+        image: my.custom.registry/linkerd-io/policy-controller:install-control-plane-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
           httpGet:
@@ -2221,7 +2221,7 @@ spec:
 ---
 apiVersion: v1
 data:
-  linkerd-config-overrides: Y29udHJvbGxlckltYWdlOiBteS5jdXN0b20ucmVnaXN0cnkvbGlua2VyZC1pby9jb250cm9sbGVyCmNvbnRyb2xsZXJJbWFnZVZlcnNpb246IGluc3RhbGwtY29udHJvbC1wbGFuZS12ZXJzaW9uCmRlYnVnQ29udGFpbmVyOgogIGltYWdlOgogICAgbmFtZTogbXkuY3VzdG9tLnJlZ2lzdHJ5L2xpbmtlcmQtaW8vZGVidWcKICAgIHZlcnNpb246IGluc3RhbGwtZGVidWctdmVyc2lvbgpoZWFydGJlYXRTY2hlZHVsZTogMSAyIDMgNCA1CmlkZW50aXR5OgogIGlzc3VlcjoKICAgIGNydEV4cGlyeTogIjIwMzAtMDgtMjZUMDc6MTM6NDdaIgogICAgdGxzOgogICAgICBjcnRQRU06IHwKICAgICAgICAtLS0tLUJFR0lOIENFUlRJRklDQVRFLS0tLS0KICAgICAgICBNSUlCd0RDQ0FXZWdBd0lCQWdJUkFKUklnWjhSdE84RXdnMVhlcGY4VDQ0d0NnWUlLb1pJemowRUF3SXdLVEVuCiAgICAgICAgTUNVR0ExVUVBeE1lYVdSbGJuUnBkSGt1YkdsdWEyVnlaQzVqYkhWemRHVnlMbXh2WTJGc01CNFhEVEl3TURneQogICAgICAgIE9EQTNNVE0wTjFvWERUTXdNRGd5TmpBM01UTTBOMW93S1RFbk1DVUdBMVVFQXhNZWFXUmxiblJwZEhrdWJHbHUKICAgICAgICBhMlZ5WkM1amJIVnpkR1Z5TG14dlkyRnNNRmt3RXdZSEtvWkl6ajBDQVFZSUtvWkl6ajBEQVFjRFFnQUUxL0ZwCiAgICAgICAgZmNSbkRjZWRMNkFqVWFYWVB2NERJTUJhSnVmT0k1Tld0eStYU1g3SmpYZ1p0TTcyZFF2UmFZYW51eEQzNkR0MQogICAgICAgIDIvSnh5aVNneEtXUmRvYXkrYU53TUc0d0RnWURWUjBQQVFIL0JBUURBZ0VHTUJJR0ExVWRFd0VCL3dRSU1BWUIKICAgICAgICBBZjhDQVFBd0hRWURWUjBPQkJZRUZJMVducnFNWUthSEhPbyt6cHlpaURxMnBPMEtNQ2tHQTFVZEVRUWlNQ0NDCiAgICAgICAgSG1sa1pXNTBhWFI1TG14cGJtdGxjbVF1WTJ4MWMzUmxjaTVzYjJOaGJEQUtCZ2dxaGtqT1BRUURBZ05IQURCRQogICAgICAgIEFpQXR1b0k1WHVDdHJHVlJ6U21SVGwycmEyOGFWOU15VFU3ZDVxblRBRkhLU2dJZ1JLQ3ZsdU9TZ0E1TzIxcDUKICAgICAgICA1MXRkcm1rSEVaUnIwcWxMU0pkSFlnRWZNems9CiAgICAgICAgLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQogICAgICBrZXlQRU06IHwKICAgICAgICAtLS0tLUJFR0lOIEVDIFBSSVZBVEUgS0VZLS0tLS0KICAgICAgICBNSGNDQVFFRUlBQWU4bmZielp1OWMvT0IyKzh4Sk0wRno3TlV3VFFhenVsa0ZOczRUSTUrb0FvR0NDcUdTTTQ5CiAgICAgICAgQXdFSG9VUURRZ0FFMS9GcGZjUm5EY2VkTDZBalVhWFlQdjRESU1CYUp1Zk9JNU5XdHkrWFNYN0pqWGdadE03MgogICAgICAgIGRRdlJhWWFudXhEMzZEdDEyL0p4eWlTZ3hLV1Jkb2F5K1E9PQogICAgICAgIC0tLS0tRU5EIEVDIFBSSVZBVEUgS0VZLS0tLS0KaWRlbnRpdHlUcnVzdEFuY2hvcnNQRU06IHwKICAtLS0tLUJFR0lOIENFUlRJRklDQVRFLS0tLS0KICBNSUlCd1RDQ0FXYWdBd0lCQWdJUWVEWnA1bERhSXlnUTVVZk1LWnJGQVRBS0JnZ3Foa2pPUFFRREFqQXBNU2N3CiAgSlFZRFZRUURFeDVwWkdWdWRHbDBlUzVzYVc1clpYSmtMbU5zZFhOMFpYSXViRzlqWVd3d0hoY05NakF3T0RJNAogIE1EY3hNalEzV2hjTk16QXdPREkyTURjeE1qUTNXakFwTVNjd0pRWURWUVFERXg1cFpHVnVkR2wwZVM1c2FXNXIKICBaWEprTG1Oc2RYTjBaWEl1Ykc5allXd3dXVEFUQmdjcWhrak9QUUlCQmdncWhrak9QUU1CQndOQ0FBUnFjNzBaCiAgbDF2Z3c3OXJqQjV1U0lUSUNVQTZHeWZ2U0ZmY3VJaXM3Qi9YRlNra3dBSFU1Uy9zMUFBUCtSMFRYN0hCV1VDNAogIHVhRzRXV3Npd0pLTm43bWdvM0F3YmpBT0JnTlZIUThCQWY4RUJBTUNBUVl3RWdZRFZSMFRBUUgvQkFnd0JnRUIKICAvd0lCQVRBZEJnTlZIUTRFRmdRVTVZdGpWVlBmZDdJN05MSHNuMkMyNkVCeUdWMHdLUVlEVlIwUkJDSXdJSUllCiAgYVdSbGJuUnBkSGt1YkdsdWEyVnlaQzVqYkhWemRHVnlMbXh2WTJGc01Bb0dDQ3FHU000OUJBTUNBMGtBTUVZQwogIElRQ043bEJGTEREdmp4NlYwK1hranBLRVJSc0pZZjVhZE12bmxvRmw0OGlsSmdJaEFOdHhobmRjcitRSlB1QzgKICB2Z1VDMGQyLzlGTXVlSVZNYis0NldUQ09qc3FyCiAgLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQpwb2xpY3lDb250cm9sbGVyOgogIGltYWdlOgogICAgdmVyc2lvbjogaW5zdGFsbC1jb250cm9sLXBsYW5lLXZlcnNpb24KcG9saWN5VmFsaWRhdG9yOgogIGNhQnVuZGxlOiBwb2xpY3kgdmFsaWRhdG9yIENBIGJ1bmRsZQogIGNydFBFTTogcG9saWN5IHZhbGlkYXRvciBjcnQKICBrZXlQRU06IHBvbGljeSB2YWxpZGF0b3Iga2V5CnByb2ZpbGVWYWxpZGF0b3I6CiAgY2FCdW5kbGU6IHByb2ZpbGUgdmFsaWRhdG9yIENBIGJ1bmRsZQogIGNydFBFTTogcHJvZmlsZSB2YWxpZGF0b3IgY3J0CiAga2V5UEVNOiBwcm9maWxlIHZhbGlkYXRvciBrZXkKcHJveHk6CiAgaW1hZ2U6CiAgICBuYW1lOiBteS5jdXN0b20ucmVnaXN0cnkvbGlua2VyZC1pby9wcm94eQogICAgdmVyc2lvbjogaW5zdGFsbC1wcm94eS12ZXJzaW9uCnByb3h5SW5pdDoKICBpbWFnZToKICAgIG5hbWU6IG15LmN1c3RvbS5yZWdpc3RyeS9saW5rZXJkLWlvL3Byb3h5LWluaXQKcHJveHlJbmplY3RvcjoKICBjYUJ1bmRsZTogcHJveHkgaW5qZWN0b3IgQ0EgYnVuZGxlCiAgY3J0UEVNOiBwcm94eSBpbmplY3RvciBjcnQKICBrZXlQRU06IHByb3h5IGluamVjdG9yIGtleQo=
+  linkerd-config-overrides: Y29udHJvbGxlckltYWdlOiBteS5jdXN0b20ucmVnaXN0cnkvbGlua2VyZC1pby9jb250cm9sbGVyCmNvbnRyb2xsZXJJbWFnZVZlcnNpb246IGluc3RhbGwtY29udHJvbC1wbGFuZS12ZXJzaW9uCmRlYnVnQ29udGFpbmVyOgogIGltYWdlOgogICAgbmFtZTogbXkuY3VzdG9tLnJlZ2lzdHJ5L2xpbmtlcmQtaW8vZGVidWcKICAgIHZlcnNpb246IGluc3RhbGwtZGVidWctdmVyc2lvbgpoZWFydGJlYXRTY2hlZHVsZTogMSAyIDMgNCA1CmlkZW50aXR5OgogIGlzc3VlcjoKICAgIGNydEV4cGlyeTogIjIwMzAtMDgtMjZUMDc6MTM6NDdaIgogICAgdGxzOgogICAgICBjcnRQRU06IHwKICAgICAgICAtLS0tLUJFR0lOIENFUlRJRklDQVRFLS0tLS0KICAgICAgICBNSUlCd0RDQ0FXZWdBd0lCQWdJUkFKUklnWjhSdE84RXdnMVhlcGY4VDQ0d0NnWUlLb1pJemowRUF3SXdLVEVuCiAgICAgICAgTUNVR0ExVUVBeE1lYVdSbGJuUnBkSGt1YkdsdWEyVnlaQzVqYkhWemRHVnlMbXh2WTJGc01CNFhEVEl3TURneQogICAgICAgIE9EQTNNVE0wTjFvWERUTXdNRGd5TmpBM01UTTBOMW93S1RFbk1DVUdBMVVFQXhNZWFXUmxiblJwZEhrdWJHbHUKICAgICAgICBhMlZ5WkM1amJIVnpkR1Z5TG14dlkyRnNNRmt3RXdZSEtvWkl6ajBDQVFZSUtvWkl6ajBEQVFjRFFnQUUxL0ZwCiAgICAgICAgZmNSbkRjZWRMNkFqVWFYWVB2NERJTUJhSnVmT0k1Tld0eStYU1g3SmpYZ1p0TTcyZFF2UmFZYW51eEQzNkR0MQogICAgICAgIDIvSnh5aVNneEtXUmRvYXkrYU53TUc0d0RnWURWUjBQQVFIL0JBUURBZ0VHTUJJR0ExVWRFd0VCL3dRSU1BWUIKICAgICAgICBBZjhDQVFBd0hRWURWUjBPQkJZRUZJMVducnFNWUthSEhPbyt6cHlpaURxMnBPMEtNQ2tHQTFVZEVRUWlNQ0NDCiAgICAgICAgSG1sa1pXNTBhWFI1TG14cGJtdGxjbVF1WTJ4MWMzUmxjaTVzYjJOaGJEQUtCZ2dxaGtqT1BRUURBZ05IQURCRQogICAgICAgIEFpQXR1b0k1WHVDdHJHVlJ6U21SVGwycmEyOGFWOU15VFU3ZDVxblRBRkhLU2dJZ1JLQ3ZsdU9TZ0E1TzIxcDUKICAgICAgICA1MXRkcm1rSEVaUnIwcWxMU0pkSFlnRWZNems9CiAgICAgICAgLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQogICAgICBrZXlQRU06IHwKICAgICAgICAtLS0tLUJFR0lOIEVDIFBSSVZBVEUgS0VZLS0tLS0KICAgICAgICBNSGNDQVFFRUlBQWU4bmZielp1OWMvT0IyKzh4Sk0wRno3TlV3VFFhenVsa0ZOczRUSTUrb0FvR0NDcUdTTTQ5CiAgICAgICAgQXdFSG9VUURRZ0FFMS9GcGZjUm5EY2VkTDZBalVhWFlQdjRESU1CYUp1Zk9JNU5XdHkrWFNYN0pqWGdadE03MgogICAgICAgIGRRdlJhWWFudXhEMzZEdDEyL0p4eWlTZ3hLV1Jkb2F5K1E9PQogICAgICAgIC0tLS0tRU5EIEVDIFBSSVZBVEUgS0VZLS0tLS0KaWRlbnRpdHlUcnVzdEFuY2hvcnNQRU06IHwKICAtLS0tLUJFR0lOIENFUlRJRklDQVRFLS0tLS0KICBNSUlCd1RDQ0FXYWdBd0lCQWdJUWVEWnA1bERhSXlnUTVVZk1LWnJGQVRBS0JnZ3Foa2pPUFFRREFqQXBNU2N3CiAgSlFZRFZRUURFeDVwWkdWdWRHbDBlUzVzYVc1clpYSmtMbU5zZFhOMFpYSXViRzlqWVd3d0hoY05NakF3T0RJNAogIE1EY3hNalEzV2hjTk16QXdPREkyTURjeE1qUTNXakFwTVNjd0pRWURWUVFERXg1cFpHVnVkR2wwZVM1c2FXNXIKICBaWEprTG1Oc2RYTjBaWEl1Ykc5allXd3dXVEFUQmdjcWhrak9QUUlCQmdncWhrak9QUU1CQndOQ0FBUnFjNzBaCiAgbDF2Z3c3OXJqQjV1U0lUSUNVQTZHeWZ2U0ZmY3VJaXM3Qi9YRlNra3dBSFU1Uy9zMUFBUCtSMFRYN0hCV1VDNAogIHVhRzRXV3Npd0pLTm43bWdvM0F3YmpBT0JnTlZIUThCQWY4RUJBTUNBUVl3RWdZRFZSMFRBUUgvQkFnd0JnRUIKICAvd0lCQVRBZEJnTlZIUTRFRmdRVTVZdGpWVlBmZDdJN05MSHNuMkMyNkVCeUdWMHdLUVlEVlIwUkJDSXdJSUllCiAgYVdSbGJuUnBkSGt1YkdsdWEyVnlaQzVqYkhWemRHVnlMbXh2WTJGc01Bb0dDQ3FHU000OUJBTUNBMGtBTUVZQwogIElRQ043bEJGTEREdmp4NlYwK1hranBLRVJSc0pZZjVhZE12bmxvRmw0OGlsSmdJaEFOdHhobmRjcitRSlB1QzgKICB2Z1VDMGQyLzlGTXVlSVZNYis0NldUQ09qc3FyCiAgLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQpwb2xpY3lDb250cm9sbGVyOgogIGltYWdlOgogICAgbmFtZTogbXkuY3VzdG9tLnJlZ2lzdHJ5L2xpbmtlcmQtaW8vcG9saWN5LWNvbnRyb2xsZXIKICAgIHZlcnNpb246IGluc3RhbGwtY29udHJvbC1wbGFuZS12ZXJzaW9uCnBvbGljeVZhbGlkYXRvcjoKICBjYUJ1bmRsZTogcG9saWN5IHZhbGlkYXRvciBDQSBidW5kbGUKICBjcnRQRU06IHBvbGljeSB2YWxpZGF0b3IgY3J0CiAga2V5UEVNOiBwb2xpY3kgdmFsaWRhdG9yIGtleQpwcm9maWxlVmFsaWRhdG9yOgogIGNhQnVuZGxlOiBwcm9maWxlIHZhbGlkYXRvciBDQSBidW5kbGUKICBjcnRQRU06IHByb2ZpbGUgdmFsaWRhdG9yIGNydAogIGtleVBFTTogcHJvZmlsZSB2YWxpZGF0b3Iga2V5CnByb3h5OgogIGltYWdlOgogICAgbmFtZTogbXkuY3VzdG9tLnJlZ2lzdHJ5L2xpbmtlcmQtaW8vcHJveHkKICAgIHZlcnNpb246IGluc3RhbGwtcHJveHktdmVyc2lvbgpwcm94eUluaXQ6CiAgaW1hZ2U6CiAgICBuYW1lOiBteS5jdXN0b20ucmVnaXN0cnkvbGlua2VyZC1pby9wcm94eS1pbml0CnByb3h5SW5qZWN0b3I6CiAgY2FCdW5kbGU6IHByb3h5IGluamVjdG9yIENBIGJ1bmRsZQogIGNydFBFTTogcHJveHkgaW5qZWN0b3IgY3J0CiAga2V5UEVNOiBwcm94eSBpbmplY3RvciBrZXkK
 kind: Secret
 metadata:
   creationTimestamp: null
diff --git a/jaeger/cmd/install_test.go b/jaeger/cmd/install_test.go
index e54daed8a..3c7e03b57 100644
--- a/jaeger/cmd/install_test.go
+++ b/jaeger/cmd/install_test.go
@@ -46,7 +46,7 @@ func TestRender(t *testing.T) {
 		t.Run(fmt.Sprintf("%d: %s", i, tc.goldenFileName), func(t *testing.T) {
 			var buf bytes.Buffer
 			// Merge overrides with default
-			if err := render(&buf, charts.MergeMaps(defaultValues, tc.values)); err != nil {
+			if err := render(&buf, charts.MergeMaps(defaultValues, tc.values), ""); err != nil {
 				t.Fatalf("Failed to render templates: %v", err)
 			}
 			testDataDiffer.DiffTestdata(t, tc.goldenFileName, buf.String())
diff --git a/test/integration/inject/inject_test.go b/test/integration/inject/inject_test.go
index de2fb644e..a63aca79d 100644
--- a/test/integration/inject/inject_test.go
+++ b/test/integration/inject/inject_test.go
@@ -10,6 +10,7 @@ import (
 	"time"
 
 	"github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/scheme"
+	"github.com/linkerd/linkerd2/pkg/flags"
 	"github.com/linkerd/linkerd2/pkg/k8s"
 	"github.com/linkerd/linkerd2/pkg/version"
 	"github.com/linkerd/linkerd2/testutil"
@@ -52,12 +53,16 @@ func parseDeployment(yamlString string) (*appsv1.Deployment, error) {
 }
 
 func TestInjectManualParams(t *testing.T) {
+	reg := "cr.l5d.io/linkerd"
+	if override := os.Getenv(flags.EnvOverrideDockerRegistry); reg != "" {
+		reg = override
+	}
 
 	injectionValidator := testutil.InjectValidator{
 		DisableIdentity:        true,
 		Version:                "proxy-version",
-		Image:                  "proxy-image",
-		InitImage:              "init-image",
+		Image:                  reg + "/proxy-image",
+		InitImage:              reg + "/init-image",
 		ImagePullPolicy:        "Never",
 		ControlPort:            123,
 		SkipInboundPorts:       "234,345",
@@ -380,9 +385,13 @@ func TestInjectAutoPod(t *testing.T) {
 	truthy := true
 	falsy := false
 	zero := int64(0)
+	reg := "cr.l5d.io/linkerd"
+	if override := os.Getenv(flags.EnvOverrideDockerRegistry); override != "" {
+		reg = override
+	}
 	expectedInitContainer := v1.Container{
 		Name:  k8s.InitContainerName,
-		Image: "cr.l5d.io/linkerd/proxy-init:" + version.ProxyInitVersion,
+		Image: reg + "/proxy-init:" + version.ProxyInitVersion,
 		Args: []string{
 			"--incoming-proxy-port", "4143",
 			"--outgoing-proxy-port", "4140",
diff --git a/test/integration/install_test.go b/test/integration/install_test.go
index 7bb75937b..a53929d5e 100644
--- a/test/integration/install_test.go
+++ b/test/integration/install_test.go
@@ -13,6 +13,8 @@ import (
 	"text/template"
 	"time"
 
+	"github.com/linkerd/linkerd2/pkg/cmd"
+	"github.com/linkerd/linkerd2/pkg/flags"
 	"github.com/linkerd/linkerd2/pkg/healthcheck"
 	"github.com/linkerd/linkerd2/pkg/k8s"
 	"github.com/linkerd/linkerd2/pkg/tls"
@@ -511,6 +513,24 @@ func helmOverridesEdge(root *tls.CA) ([]string, []string) {
 		"--set", "linkerdVersion=" + TestHelper.GetVersion(),
 		"--set", "namespace=" + TestHelper.GetVizNamespace(),
 	}
+
+	if override := os.Getenv(flags.EnvOverrideDockerRegistry); override != "" {
+		coreArgs = append(coreArgs,
+			"--set", "policyController.image.name="+cmd.RegistryOverride("cr.l5d.io/linkerd/policy-controller", override),
+			"--set", "proxy.image.name="+cmd.RegistryOverride("cr.l5d.io/linkerd/proxy", override),
+			"--set", "proxyInit.image.name="+cmd.RegistryOverride("cr.l5d.io/linkerd/proxy-init", override),
+			"--set", "controllerImage="+cmd.RegistryOverride("cr.l5d.io/linkerd/controller", override),
+			"--set", "debugContainer.image.name="+cmd.RegistryOverride("cr.l5d.io/linkerd/debug", override),
+		)
+		vizArgs = append(vizArgs,
+			"--set", "metricsAPI.image.registry="+override,
+			"--set", "tap.image.registry="+override,
+			"--set", "tapInjector.image.registry="+override,
+			"--set", "dashboard.image.registry="+override,
+			"--set", "grafana.image.registry="+override,
+		)
+	}
+
 	return coreArgs, vizArgs
 }
 
@@ -757,18 +777,40 @@ func TestOverridesSecret(t *testing.T) {
 		knownKeys := tree.Tree{
 			"controllerLogLevel": "debug",
 			"heartbeatSchedule":  "1 2 3 4 5",
-			"identity": map[string]interface{}{
-				"issuer": map[string]interface{}{},
+			"identity": tree.Tree{
+				"issuer": tree.Tree{},
 			},
 			"identityTrustAnchorsPEM": extractValue(t, "identityTrustAnchorsPEM"),
-			"proxyInit": map[string]interface{}{
+			"proxyInit": tree.Tree{
 				"ignoreInboundPorts": skippedInboundPorts,
 			},
 		}
 
+		if reg := os.Getenv(flags.EnvOverrideDockerRegistry); reg != "" {
+			knownKeys["controllerImage"] = reg + "/controller"
+			knownKeys["debugContainer"] = tree.Tree{
+				"image": tree.Tree{
+					"name": reg + "/debug",
+				},
+			}
+			knownKeys["policyController"] = tree.Tree{
+				"image": tree.Tree{
+					"name": reg + "/policy-controller",
+				},
+			}
+			knownKeys["proxy"] = tree.Tree{
+				"image": tree.Tree{
+					"name": reg + "/proxy",
+				},
+			}
+			knownKeys["proxyInit"].(tree.Tree)["image"] = tree.Tree{
+				"name": reg + "/proxy-init",
+			}
+		}
+
 		// Check for fields that were added during upgrade
 		if TestHelper.UpgradeFromVersion() != "" {
-			knownKeys["proxyInit"].(map[string]interface{})["ignoreOutboundPorts"] = skippedOutboundPorts
+			knownKeys["proxyInit"].(tree.Tree)["ignoreOutboundPorts"] = skippedOutboundPorts
 		}
 
 		if TestHelper.GetClusterDomain() != "cluster.local" {
@@ -776,13 +818,13 @@ func TestOverridesSecret(t *testing.T) {
 		}
 
 		if TestHelper.ExternalIssuer() {
-			knownKeys["identity"].(map[string]interface{})["issuer"].(map[string]interface{})["issuanceLifetime"] = "15s"
-			knownKeys["identity"].(map[string]interface{})["issuer"].(map[string]interface{})["scheme"] = "kubernetes.io/tls"
+			knownKeys["identity"].(tree.Tree)["issuer"].(tree.Tree)["issuanceLifetime"] = "15s"
+			knownKeys["identity"].(tree.Tree)["issuer"].(tree.Tree)["scheme"] = "kubernetes.io/tls"
 		} else {
 			if !TestHelper.Multicluster() {
-				knownKeys["identity"].(map[string]interface{})["issuer"].(map[string]interface{})["crtExpiry"] = extractValue(t, "identity", "issuer", "crtExpiry")
+				knownKeys["identity"].(tree.Tree)["issuer"].(tree.Tree)["crtExpiry"] = extractValue(t, "identity", "issuer", "crtExpiry")
 			}
-			knownKeys["identity"].(map[string]interface{})["issuer"].(map[string]interface{})["tls"] = map[string]interface{}{
+			knownKeys["identity"].(tree.Tree)["issuer"].(tree.Tree)["tls"] = tree.Tree{
 				"crtPEM": extractValue(t, "identity", "issuer", "tls", "crtPEM"),
 				"keyPEM": extractValue(t, "identity", "issuer", "tls", "keyPEM"),
 			}