Pull-Through Caches
A pull-through cache is a mirror of one public registry, living in your own tenant and filled on demand. You pull an image through it instead of from the public registry; the first pull fetches the image from upstream and keeps a copy, and later pulls for the same image are served from that copy.
It is useful when a public registry rate-limits you, when a build pipeline pulls the same base images all day, or when you want your workloads to reach one endpoint — registry.sweden.frostmoln.cloud — for both your own images and the public ones you depend on.
One cache mirrors one upstream. If you pull from GitHub Container Registry, Docker Hub and Quay, that is three caches, each with its own namespace and its own pull path. There is nothing to configure beyond choosing the upstream.
Your registry has to be enabled first
A cache lives in your registry, so enable the registry before creating one. If you have not, creating a cache is refused.
Which upstreams you can mirror
Frostmoln offers a fixed list of public registries — you choose one from that list, not a URL of your own. At the time of writing it includes Docker Hub, GitHub Container Registry, Quay, the Kubernetes registry, Microsoft Container Registry and Amazon ECR Public.
Read the current list from the portal or the CLI, not from here
The list is served by the API, so the portal's picker and fm registry cache list always show exactly what is on offer today, and fm registry cache create completes the keys from that same server-side list — so you never have to memorise them or keep a copy. Treat those as the answer; the names above are examples and may age. An upstream that is withdrawn stops being offered for new caches — a cache you already have keeps working and can still be deleted.
Docker Hub needs your own Docker Hub credentials
A Docker Hub cache is refused unless you supply a Docker Hub username and password (a personal access token works, and is the better choice).
This is not an arbitrary rule. A cache that pulls from Docker Hub anonymously draws on an anonymous allowance that is shared with other Frostmoln customers on the same outbound address. One busy anonymous mirror uses it up and every other customer behind that address starts getting rate-limited — for pulls that have nothing to do with your cache. Supplying your own credentials puts the pulls on your Docker Hub account and your own allowance, which is also the only way your cache gets the limits you are paying Docker for.
Other upstreams do not require credentials today, but you may still supply them — for example to reach a private repository you have access to.
Which upstreams demand credentials is shown where you create the cache
The portal marks them, and the CLI tells you if you leave them out. The rule is enforced by the API, so the CLI and Terraform meet the same refusal as the portal.
Create a cache
In the portal, open Container Registry and find the Pull-through caches section there, then choose Add cache. Pick the upstream, supply credentials if it asks for them, and create it. An upstream you already hold a cache for is shown greyed out rather than hidden — one cache per upstream. The new cache's pull path appears on its row; select it, or the copy icon beside it, to copy it.
From the CLI:
fm registry cache list # what you have, and what is on offer
fm registry cache create ghcr
fm registry cache delete ghcr # --force to skip the confirmationAn upstream that needs credentials takes them in one of two safe ways. At a terminal, give the username and let the command ask for the password — it is not echoed, and it never reaches your shell history:
fm registry cache create dockerhub --username acmeIn CI, where there is no prompt, feed the password on stdin:
printf '%s' "$DOCKERHUB_TOKEN" | fm registry cache create dockerhub \
--username acme --password-stdinUse printf '%s' rather than echo, for the reason spelled out under Logging in — echo mangles some values silently, and a mangled upstream token fails as a broken pull rather than as a bad password.
There is also a --password flag. Treat it as a last resort: the value is visible in ps on a shared machine and kept in your shell history, so prefer either of the two above. Running non-interactively with no password at all is not a trap — the command fails and names --password-stdin rather than hanging.
With Terraform:
resource "frostmoln_container_registry_cache" "ghcr" {
upstream = "ghcr"
depends_on = [frostmoln_container_registry.this]
}
resource "frostmoln_container_registry_cache" "dockerhub" {
upstream = "dockerhub"
username = var.dockerhub_username
password = var.dockerhub_token # sensitive — see the warning below
depends_on = [frostmoln_container_registry.this]
}The upstream password is written to Terraform state
Terraform keeps every value you pass it, so your Docker Hub token ends up in state — plaintext JSON in your working directory if you have not configured a remote backend. The warning on the registry page applies here in full: use an encrypted remote backend, keep terraform.tfstate* out of git, and take the value from a variable rather than writing it into the .tf file.
Whichever surface you use, you get back the cache's pull path. That is the one thing you need afterwards.
The credentials are not checked when you create the cache
Frostmoln does not test them against the upstream, so a typo does not fail here — it fails later, as a pull that cannot fetch the image. If a cache never serves anything, suspect the credentials first.
Pull through the cache
Prefix the image with the cache's pull path in place of the upstream host, and keep the rest of the reference exactly as it was.
An image you would normally pull as:
ghcr.io/acme/widget:1.2.3becomes:
registry.sweden.frostmoln.cloud/t-3f2a9c1e4b7d4a2f8e1c5d6b7a8f9e0d-cache-ghcr/acme/widget:1.2.3Your own pull path is shown in the portal and by fm registry cache list; the t-...-cache-ghcr part above is an example.
Use the same credential you already use for your own images. docker login is per host, and a cache is on the same host as your namespace, so one login covers both. You do not mint anything new for a cache — the credentials you already hold are extended to it automatically when it is created.
# Log in once (single-quote the username — it contains a $)
docker login registry.sweden.frostmoln.cloud -u 'robot$t-...-a1b2c3d4'
# Pull through the cache
docker pull \
registry.sweden.frostmoln.cloud/t-3f2a9c1e4b7d4a2f8e1c5d6b7a8f9e0d-cache-ghcr/acme/widget:1.2.3A pull credential is enough. podman takes the same commands.
Docker Hub official images keep their library/ prefix
docker pull nginx works because the Docker CLI expands it to docker.io/library/nginx. Nothing expands it for you here, so through a Docker Hub cache the reference is registry.sweden.frostmoln.cloud/<your-cache-namespace>/library/nginx:1.27. Leaving out library/ gives you a "not found" that looks like a broken cache.
From a managed Kubernetes cluster
Nothing new is needed. The cache is on the same host as your registry, so the image pull secret you already have covers it — you change only the image reference:
spec:
imagePullSecrets:
- name: frostmoln-registry
containers:
- name: my-app
image: registry.sweden.frostmoln.cloud/t-3f2a9c1e4b7d4a2f8e1c5d6b7a8f9e0d-cache-ghcr/acme/widget:1.2.3If you do not have that Secret yet, build it exactly as for your own images — see Pull from a managed Kubernetes cluster, including the note that the cluster's VPC needs a gateway for outbound access.
Tags, :latest and how current a cache is
A pull by tag asks the upstream every time the cache can reach it. Whenever you pull acme/widget:1.2.3 or acme/widget:latest through a cache, the cache first asks the upstream which image that tag points at now, and gives you that one — the copy it already holds if nothing changed, otherwise the new image, which it then keeps. There is no cache lifetime to wait out: :latest through a cache follows the upstream at the moment you pull.
When the upstream says a tag no longer exists, a pull by that tag fails, even if the cache still holds a copy. Removing a tag upstream removes it for pulls through the cache too.
When the cache cannot get an answer from the upstream — it is unreachable, or refuses the check with a rate-limit response — what you get depends on the image:
- A single-platform image pulled by tag is served from the cache — even if the tag has since moved upstream. The pull succeeds and nothing tells you the image may be out of date.
- A multi-platform image pulled by tag with Docker fails. Most public images are multi-platform (one tag covering
linux/amd64,linux/arm64and so on), and the cache does not keep them under the tag — see Multi-platform images below — so there is nothing under the tag to fall back to. - A pull by the digest of an image the cache holds — a single-platform image, or one platform of a multi-platform image — is served from the cache, with or without the upstream.
To keep pulls working through an upstream outage, pin a platform digest
The digest docker pull prints for a multi-platform image, and the one in docker inspect's RepoDigests, is the digest of the index — the list of platforms. The cache does not keep that index as it is, so a pull by that digest needs the upstream to answer. Pin the digest of the platform image you run instead:
image: <pull-path>/acme/widget@sha256:<linux/amd64 digest>To find it, list the platforms of the upstream image — the platform digests are the same upstream and in the cache:
docker buildx imagetools inspect ghcr.io/acme/widget:1.2.3
docker manifest inspect ghcr.io/acme/widget:1.2.3 # the same, as JSONSkip entries whose platform is unknown/unknown — those are build attestations, not images. Pull the pinned reference through the cache once so the cache holds it, and keep pulling it: like any other image, one nobody pulls for 7 days is removed.
Images nobody pulls are removed. An image that has not been pulled through the cache for 7 days is removed from it at the next daily check. The next pull fetches it from the upstream again, as long as the upstream answers — and it keeps a cache from holding on to storage for images you stopped using.
Multi-platform images
When you pull a multi-platform image through a cache with Docker, Docker first fetches the index by its tag, then the one platform image it needs by that image's digest. The cache stores what it was asked for:
- The platform image you pulled — for example
linux/amd64— under its own digest, without a tag, because it was fetched by digest. - Shortly after, an index of its own that lists the platforms pulled through the cache so far. It has no tag either, and its digest is never the upstream's: the cache builds this index itself, so its digest differs even when every platform has been pulled.
When you browse the cache, this is what you see: first the platform image, and once the cache's own index exists, that index in its place — untagged, with a digest that matches neither the upstream nor RepoDigests. Nothing is wrong with the image, and pulls by tag keep working while the upstream answers: the cache asks the upstream for the current index each time, as described above.
The digest shown when you browse is the cache's, not one to pin. To pin a platform image, take its digest from the upstream as shown in the tip above.
See what a cache holds
In the portal, choose Browse on a cache's row in the Pull-through caches section. You get the repositories the cache holds and, for each one, its images: tags, digest, size, when the cache fetched it and when it was last pulled. Select a tag, or the copy icon beside it, to copy the full reference to pull it through the cache. A multi-platform image pulled with Docker appears without a tag and under a digest of its own — see Multi-platform images.
From the CLI, name the cache by its upstream's key, as everywhere else:
fm registry cache repository list ghcr
fm registry cache image list ghcr acme/widget
fm registry cache image list ghcr acme/widget --all -o jsonRepository names are the upstream's own — acme/widget, or library/nginx through a Docker Hub cache — without the cache's namespace in front.
Remove images from a cache
You can remove one image from a cache without deleting the cache. In the portal, press Remove on the image's row; from the CLI:
fm registry cache image delete ghcr acme/widget sha256:1b0c8dd1...
fm registry cache image delete ghcr acme/widget sha256:1b0c8dd1... --forceIt takes a digest, never a tag, for the same reason as deleting your own images: a tag can move between listing and deleting. Every tag on that image goes with it, and a multi-architecture image is removed by its index, not by its per-platform parts.
It is not permanent. Everything in a cache is a copy of something that still exists upstream, so the next pull of that image simply fetches it again. Remove an image to free space now rather than waiting for it to age out. It does not stop anyone pulling the image — to stop pulls through a cache, delete the cache.
Removing does not get you a fresher image
A pull by tag already asks the upstream every time, so there is no stale copy to clear while the upstream is answering. And while the upstream is unreachable or rate-limiting you, the cached copy of a single-platform image or of a pinned platform digest is the only thing a pull can get — remove it then, and pulls of that image fail until the upstream answers again.
Cached images count against your storage allowance
One allowance, shared with your own images — and a cache fills itself
Everything a cache stores counts against the same storage allowance as your own namespace. There is one figure and one limit for your whole registry: your images plus every cache you own.
A cache fills on pull, not on push. So it can consume your allowance without you ever pushing anything — and it does so as a side effect of an ordinary docker pull by a pipeline, a cluster or a colleague. Even a request that only asks for a manifest triggers a full fetch of the image.
The practical consequence: a cache in front of a busy public registry can fill your allowance on its own, and the first sign is a push of your own images being refused because there is no room left.
Watch the storage figure in the portal under Storage used, or with fm registry status, after you add a cache — it is the aggregate, so it moves when a cache fills. Each cache's own figure is in the Storage column of the Pull-through caches section. It counts each stored layer once within that cache, so it tells you which cache is filling up, not the sum of the image sizes you pulled.
If you are running out of room, the fastest reclaim is usually to delete a cache you are no longer pulling through — see Delete a cache — or to remove the images you no longer need from one you keep. Neither costs you anything permanent. Deleting your own images is covered under Freeing space.
Changing the upstream credentials
There is no update. Credentials are write-only: nothing reads them back, and there is no field to change. To rotate a Docker Hub token, or to fix one you typed wrong, delete the cache and create it again with the new values.
That is not as heavy as it sounds — you lose the cached copies, and the next pull re-fetches them. The pull path is derived from your tenant and the upstream, so it comes back identical and nothing referencing it has to change.
Delete a cache
In the portal, remove the row from the Pull-through caches section of the Container Registry page. From the CLI:
fm registry cache delete ghcr
fm registry cache delete ghcr --force # same, unattendedDeletion takes the upstream's key — the same short name you created it with — and it is what the portal row and fm registry cache list show.
Deleting a cache deletes the images it had cached, and does not ask twice. That is safe in a way that deleting your own images is not: everything in a cache is a copy of something that still exists at the upstream, so nothing you made is lost. Re-create the cache, pull again, and the content comes back.
Two things do change immediately:
- Your stored-bytes figure drops as the cached content goes. That is the point, if you were deleting to free space.
- Anything still pulling through that path starts failing. Grep your manifests, Helm values and pipeline files for the pull path before you delete a cache that has been in use.
The stored upstream credentials go with it. If you delete a cache in order to change them, have the new username and token to hand before you start.
How many caches you can have
Your tenant may hold a limited number of caches at once. Creating one past the limit is refused, and the remedy is to delete a cache you no longer pull through.
Read the number where you create the cache
The portal shows the limit and stops offering Add cache once you reach it, and fm registry cache list reports it alongside what you hold. That figure comes from the API and is the authoritative one — this page deliberately does not print a number, so that it cannot quietly go out of date.
When creating a cache is refused
| What you see | What it means |
|---|---|
| The upstream is not one that can be mirrored | It is not on the offered list, or it has been withdrawn since you scripted it. Read the current list and pick from it. |
| Credentials are required for this upstream | Docker Hub, today. Supply your own username and token — see above. |
| You already have a cache for this upstream | One cache per upstream, and there is nothing to update. Delete it and create it again if you are changing the credentials. |
| You have reached the limit | Delete a cache you no longer pull through. |
| Your registry is not enabled | Enable it first. |
A suspended registry also refuses to create a cache, as it refuses every other write. Pulls through caches you already have keep working, with the pull credentials you already hold — the same as for your own images. See If your registry is suspended.