# k8s-image-swapper rewrites pod images to point at your own registry

> k8s-image-swapper is a Kubernetes mutating admission webhook that mirrors third party images into Amazon ECR or Google Container Registry and rewrites the pod spec to reference the copy. It suits cluster operators who need air-gapped pulls or protection from upstream rate limits, and it only handles Amazon ECR and Google Container Registry.

**estahn/k8s-image-swapper** — Mirror images into your own registry and swap image references automatically.

- Repository: https://github.com/estahn/k8s-image-swapper
- Website: https://estahn.github.io/k8s-image-swapper/
- Stars: 633 · Forks: 53
- Language: Go
- License: MIT
- Published: 2026-10-21 · Updated: 2026-10-21 · Language: en
- Canonical page: https://hysenlabs.com/projects/estahn-k8s-image-swapper

## It is an admission webhook, not a pull-through proxy

The project is described as a mutating webhook for Kubernetes, downloading images into your own registry and pointing the images to that new location, and it is explicitly positioned as an alternative to a docker pull-through proxy. That distinction is the whole design. A pull-through proxy sits between the kubelet and the upstream registry, caching on demand. A mutating webhook sits in the API server's admission path and changes the object before it is persisted, so the pod spec that lands in etcd already points at your registry and the pull happens directly from you. That means the mirror is a one-time copy per image rather than a cache, and the cost profile is different: you pay for storage in your registry instead of bandwidth through a proxy, and the README names that as one of the benefits, reducing data traffic and therefore cost. The topics list is short and precise, golang, kubernetes, kubernetes-webhook and mutating-webhook, and the README notes that Amazon ECR and Google Container Registry are currently supported.

## The failure modes it defends against are all upstream problems

The benefits section lists what the tool protects against, and every item is a failure of the external registry rather than of your cluster. External registry failure, with a linked example of a quay.io outage. Image pull rate limiting, with a link to the Docker Hub rate limit announcement. Accidental image changes, and removal of images. The stated effect on the cluster is improved availability, reliability, durability and resiliency, because the third party images are mirrored into your own registry. The mechanism of protection is that the pod references a copy you hold, so an upstream that goes away, starts rate limiting, or quietly changes a tag no longer affects you. The README also claims the tool can be used in air-gapped environments without the need to change manifests, which is the strongest version of the value proposition, since in a normal air gap the manifest is the thing you cannot reach the registry from, and the webhook has already rewritten it.

## The Go dependency list shows how the copy is made

The go.mod file gives a clear picture of the implementation. The module is github.com/estahn/k8s-image-swapper requiring Go 1.25.0. The registry clients are the cloud SDKs, cloud.google.com/go/artifactregistry and google.golang.org/api for Google, and github.com/aws/aws-sdk-go for Amazon. The actual image copy is done with github.com/containers/image/v5, which is the library behind skopeo and is the piece that understands registry protocols, authentication and image manifests. The Kubernetes side uses slok/kubewebhook/v2 to serve the admission endpoint, with k8s.io/api, apimachinery and client-go at 0.33.4. Support code comes from spf13/cobra and spf13/viper for the command line and configuration, alitto/pond as a worker pool, dgraph-io/ristretto as an in-memory cache, go-co-op/gocron for scheduling, rs/zerolog for logging, prometheus/client_golang for metrics and evanphx/json-patch for the JSON patch that mutates the pod spec. The presence of terratest in the requirements also shows that the project tests its own Helm deployment.

## The container image is a single binary on Alpine with skopeo alongside

The Dockerfile is short and tells you about the packaging choices. It builds from alpine:3.24.1 and installs skopeo at 1.2.0 or newer from the Alpine community repository, then copies the compiled k8s-image-swapper binary to the root of the image and sets it as the entrypoint. The comment above the FROM line is candid about the reason, noting that alpine is used for now due to easier installation of skopeo, and that distroless will be used after incorporating skopeo into the webhook directly. So the current design shells out to an external binary rather than linking the copy logic directly into the webhook, and the plan is to fold it in. The commented-out lines above show the earlier intent, a multi-stage build copying skopeo out of a quay.io/skopeo image onto a distroless or Debian base. The image carries a full set of OCI and label-schema annotations including the source URL, the vendor, the revision and the build date.

## Configuration is a YAML file and the build is a Makefile with an e2e target

The repository root holds a .k8s-image-swapper.yml configuration file, and the documentation is published as a site with a mkdocs.yml, a docs/ directory and a comprehensive guide plus a list of configuration options. The deploy/ directory holds the Kubernetes manifests, and there is a Helm chart published to Artifact Hub under estahn/k8s-image-swapper. The Makefile has the usual small set of targets. setup runs go mod download and go mod tidy. test runs the suite with LC_ALL=C, the race detector, coverage across all packages and a five minute timeout. fmt runs gofmt and goimports over the tree, lint runs golangci-lint, and cover opens the coverage report in a browser. The target worth noting is e2e, which runs TestHelmDeployment in the test directory, so the Helm installation path is exercised against a real cluster rather than mocked. The release side uses goreleaser for the binaries, a .releaserc file for semantic-release, and a package.json whose only dependency group is the semantic-release toolchain.

## A webhook is a privileged position in the API server, and that is the real risk

The design puts a component with registry credentials on the path of every pod creation in the cluster, and that is worth thinking about clearly before adopting it. The webhook receives the pod spec, downloads the image, uploads it to your registry, and returns a patched spec. The failure modes follow from that position. If the mirror step fails, the pod cannot be admitted, so a transient network problem or a rate limit on the source registry becomes a scheduling failure. If the webhook is down entirely, the default behaviour of a mutating webhook with a failure policy that rejects is to block pod creation, so the availability of your cluster's admission path now depends on the availability of the image copy. The mitigation is the one the documentation implies, which is that you should mirror the images you depend on in advance and let the webhook find them already present. The cost of the tool is therefore not the mirroring, it is the coupling between your admission path and your registry credentials.

## Only two registries are supported, which defines the scope

The README states that Amazon ECR and Google Container Registry are currently supported, and the dependency list bears that out, with the AWS SDK and the Google Artifact Registry client as the only registry clients present. There is no generic registry client in the tree and no mention of Docker Hub, GitLab or a self-hosted Harbor as a destination, so the tool as shipped mirrors into those two clouds and not into an arbitrary registry you run yourself. That is a hard boundary for anyone whose images are required to live in a company registry for policy reasons, since the whole point of mirroring is control and the control is limited to two cloud providers. There is also the source side to consider. The tool copies images referenced by name from wherever they are today, so the source can be any registry the copy library can reach, and the destination is what is constrained. For a team on Azure, or one with an on-premises Harbor, the realistic path is to read the code and add a destination, which is a Go change against the same containers/image library rather than a rewrite, but it is work the project has not done for you.

## Where k8s-image-swapper is the wrong choice

Three situations argue against it. The first is a cluster that already has a registry mirror in front of it. A pull-through proxy configured on the kubelet gives you most of the availability benefit with none of the admission coupling, and it needs no webhook, no extra API server traffic and no registry write credentials inside the cluster. The second is a workload whose images are already in a registry you control. If your deployments already reference internal images, there is nothing to mirror and the webhook becomes a no-op that you still have to run. The third is a security environment where a component holding push credentials to your registry should not sit on the admission path. In a cluster with strict pod security standards or with network policies that assume the API server path is trusted, adding a service that rewrites image references and talks to a registry is a change with a blast radius wider than the feature suggests. The honest reading is that this tool is for a specific situation, a cluster that pulls public images and needs them held locally, and outside that situation a registry mirror or a CI step that pushes your own tagged copies is the more contained answer.

## Conclusion

Adopt k8s-image-swapper when you run a cluster that pulls from public registries and need those images mirrored into a registry you control, either for air-gapped access or to avoid upstream rate limits and outages. Skip it if you already pull through a registry mirror or if your images live in a registry other than Amazon ECR and Google Container Registry, since those are the only two supported. Verify first that the webhook's own deployment and its service account permissions are set up before you let it touch workloads, because a failed mirror becomes a failed pull.

## FAQ

### What does k8s-image-swapper do?

It is a mutating webhook for Kubernetes that downloads images into your own registry and points the pod spec at that new location, described as an alternative to a docker pull-through proxy. Amazon ECR and Google Container Registry are currently supported.

### What problems does k8s-image-swapper protect against?

The README lists external registry failure, image pull rate limiting, accidental image changes and removal of images. The stated effect is better availability, reliability, durability and resiliency for the cluster, and it also claims to work in air-gapped environments without changing manifests.

### How is k8s-image-swapper packaged?

The Dockerfile builds from alpine:3.24.1, installs skopeo 1.2.0 or newer from the Alpine community repository, copies the k8s-image-swapper binary in and sets it as the entrypoint. A comment notes that distroless will be used once skopeo is incorporated into the webhook directly.

### Which registries can k8s-image-swapper mirror images into?

Amazon ECR and Google Container Registry are currently supported, and the dependency list matches that, with the AWS SDK and the Google Artifact Registry client as the only registry clients. There is no generic registry destination in the tree.

### Is k8s-image-swapper still maintained?

The repository is not archived and the last push was on 2026-10-20. Recent tags are v1.6.9 from 2026-10-01, v1.6.8 from 2026-09-01 and v1.6.7 from 2026-08-01, the licence is MIT, and the Helm chart is published to Artifact Hub under estahn/k8s-image-swapper.

## Sources

- [estahn/k8s-image-swapper on GitHub](https://github.com/estahn/k8s-image-swapper)
- [License: MIT](https://github.com/estahn/k8s-image-swapper/blob/main/LICENSE)
- [Project website](https://estahn.github.io/k8s-image-swapper/)
- [README](https://github.com/estahn/k8s-image-swapper/blob/main/README.md)
- [Releases](https://github.com/estahn/k8s-image-swapper/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/estahn-k8s-image-swapper
