← Back to Blog
DevOps

K3s System Images GHCR Architecture Diagram: Registry Mirror & Air-Gap Pull Path

On 1 October 2026, Vitor Savian wrote on the K3s documentation blog that system images move to the GitHub Container Registry. The change starts with K3s v1.40, which the post says is expected in July 2027. Clusters that pull defaults and can reach GHCR need no new config. Mirrors, a private registry, --system-default-registry, and air-gap tarballs do. The post says the announcement is early so operators have several releases to prepare. v1.40 is not described as shipped on that date.

K3s System Images GHCR Architecture Diagram: Registry Mirror & Air-Gap Pull Path
The default pull is the node to ghcr.io/k3s-io. The mirror rewrites the host name only, and the repository stays k3s-io. A private host uses --system-default-registry with the path still k3s-io. The air-gap tarball goes into /var/lib/rancher/k3s/agent/images/. An optional rewrite to docker.io/rancher goes only through https://index.docker.io. Dashed misses: an old rancher tarball, an empty flag still drawn as docker.io, or a rewrite on the default Docker Hub endpoint. The planned move starts at v1.40, not a ship date. There is no numeric Docker Hub limit.

What this K3s GHCR system images architecture diagram shows

Only the images K3s deploys itself. The post lists CoreDNS, Traefik, local-path-provisioner, metrics-server, klipper-helm, klipper-lb, and the pause image. Today those names are under docker.io/rancher. From v1.40 they are under ghcr.io/k3s-io. Application workloads keep whatever registry they use now. Releases before v1.40 keep the images they shipped with, so nothing moves until that cluster is upgraded. K3s will still publish the same images on Docker Hub under rancher. It will not pull them from there by default.

The problem this architecture is solving

The post says images in the rancher organization on Docker Hub were not rate-limited because of a paid arrangement between SUSE and Docker. That arrangement is ending, and pulls become subject to Docker Hub's image pull usage limits. It does not state the numeric limit. It points at k3s-io/k3s issue 14561 for more, which this article does not treat as a second source. Public GHCR images, the post says, do not have that kind of limit, and K3s already published some images there. Leaving even pause on Docker Hub would still expose pulls to the limit, so pause moves too. With the paid exception gone, the post says there is no longer a reason to prefer Docker Hub for these images.

Main components and trust boundaries

Default path: the node resolves ghcr.io/k3s-io/<name>. If outbound traffic cannot reach GHCR, that path fails even when Docker Hub is still allowed. The post's affected list is mirrors, --system-default-registry, the embedded registry mirror, air-gap tarballs, and restricted egress.

--system-default-registry swaps the registry host and keeps the repository path. The post's example, k3s server --system-default-registry registry.example.com:5000, makes v1.40 pull registry.example.com:5000/k3s-io/<name>. Those images have to exist under k3s-io/ before the upgrade. The post says to take k3s-images.txt for v1.40 from the GitHub releases page, then pull, retag, and push. An unset flag, or an empty string, now means ghcr.io. The post says an empty string used to mean docker.io. You can still set docker.io explicitly. The post says that likely fails unless registries.yaml rewrites k3s-io to rancher, because the names are no longer under rancher/.

The rewrite sample maps ^k3s-io/(.*) to rancher/$1 with endpoint https://index.docker.io, so docker.io/k3s-io/<name> is pulled as docker.io/rancher/<name>. Rewrites do not apply to the default Docker Hub endpoint. A registries.yaml that only mirrors docker.io does not cover ghcr.io. Add the same endpoint under ghcr.io and the path stays k3s-io. The embedded mirror lists ghcr.io beside docker.io and registry.k8s.io with no endpoint. Auth or custom TLS needs a configs entry. Update the file on every node and restart K3s there.

Request or data path, step by step

Direct nodes. After the v1.40 upgrade, K3s requests the system image from ghcr.io/k3s-io. No mirror entry is consulted unless you added one. Workload images are not rewritten by this change.

Mirror or private host. registries.yaml sends the ghcr.io name to your endpoint and keeps k3s-io in the path, unless a rewrite says otherwise. --system-default-registry does the host swap inside K3s before that pull. If the private registry was filled from old rancher names, the new path misses.

Air gap. The v1.40 tarballs, named in the post as k3s-airgap-images artifacts, and k3s-images.txt reference the GHCR names. Older tarballs still say rancher. Load one into a registry and the tags become <host>/rancher/... while K3s asks for <host>/k3s-io/.... The post says to use the tarball that matches the K3s version. Manual installs drop that tarball into /var/lib/rancher/k3s/agent/images/ on each node before the binary upgrade. Registry installs push the k3s-io/ path. Scripts that mirror, scan, or retag docker.io/rancher/... have to change with them. A registries.yaml change and a restart apply on every node. One node left on the old file keeps asking the old way.

The diagram: labeled boxes

  • Default arrow: K3s node to ghcr.io/k3s-io for the seven system images, pause included. Workload pods do not ride that arrow.
  • Mirror arrow: registries.yaml endpoint for ghcr.io, path still k3s-io. Embedded mirror lists the host with no endpoint.
  • Private host: --system-default-registry keeps k3s-io/. Empty flag is ghcr.io, not docker.io.
  • Rewrite: k3s-io to rancher only if you still want Docker Hub, and only via a non-default endpoint such as index.docker.io.
  • Air gap: matching tarball into /var/lib/rancher/k3s/agent/images/, or a registry push under k3s-io. Dashed misses: an old rancher tarball, an empty flag still drawn as docker.io, or a rewrite on the default Docker Hub endpoint.

What the source does not claim (preview, case study, or limits)

This is a 1 October 2026 notice, not a case study, and it does not say v1.40 has shipped. July 2027 is what the post calls expected. No pull-rate integer appears. Docker Hub publication under rancher continues. The default pull is what moves. Tag lists and the full registries.yaml schema are not on this page. The adopter-list note at the end is unrelated to the pull path.

FAQ

If nodes pull K3s system images from Docker Hub today, what changes at v1.40?

The 1 October 2026 post says nothing, if you use the defaults and the nodes can reach the GitHub Container Registry. Starting with v1.40, which the post says is expected in July 2027, K3s pulls its own system images from ghcr.io/k3s-io instead of docker.io/rancher. Your workloads keep their current registries. Releases before v1.40 keep the images they shipped with until you upgrade that cluster.

Why do old air-gap tarballs fail on v1.40?

The post says v1.40 air-gap tarballs and k3s-images.txt reference ghcr.io images. Older tarballs still carry rancher names. Load an old tarball into a private registry and the images land under rancher while v1.40 asks for k3s-io. Use the tarball that matches the release. For a manual load, put that tarball in /var/lib/rancher/k3s/agent/images/ on each node before you upgrade the binary. If you push to a private registry, keep the k3s-io path.

What does an empty --system-default-registry mean after this change?

The post says an unset or empty flag now defaults to ghcr.io. An empty string previously defaulted to docker.io. You can still set docker.io, but the post says that likely fails unless registries.yaml rewrites k3s-io to rancher. Rewrites are not applied on the default Docker Hub endpoint, so the sample points the endpoint at https://index.docker.io. If you set the flag to your own host, K3s keeps the repository path and looks for k3s-io on that host.

Conclusion

v1.40, expected July 2027, points K3s system images at ghcr.io/k3s-io. Defaults that can reach GHCR stay quiet. Mirrors, embedded mirrors, --system-default-registry, and air-gap tarballs must use the k3s-io path, and an empty registry flag no longer means Docker Hub. More diagrams are on the ByteDiagram blog.