Pull Docker Hub images through the dependency proxy with your own Docker Hub token
fremforge does not pull Docker Hub images on your behalf with a shared account. Docker’s terms allow access for your own internal purposes, and Docker Hub limits anonymous pulls per IP address, shared by everyone behind that address. So Docker Hub works the way GitLab’s dependency proxy and Amazon ECR pull-through caches do it: your organisation brings its own Docker Hub token, and the dependency proxy pulls and caches images with it.
You then have two ways to pull a Docker Hub image:
| Route | Image reference | Credential | Limits |
|---|---|---|---|
| Through your organisation’s dependency proxy | frem.sh/<org>/dependency-proxy/docker.io/<namespace>/<repository>:<tag> | Your organisation’s Docker Hub token, stored once | Your Docker Hub account’s limits. A cached image does not count against Docker Hub again. |
| Directly from Docker Hub | docker.io/<namespace>/<repository>:<tag> | None (anonymous), or your own docker login | Docker Hub’s limit for anonymous pulls, per IP address |
fremforge-hosted runners no longer use a shared Docker Hub mirror. Until your organisation has
stored its Docker Hub token, a plain docker pull node:24 in a workflow is an anonymous pull
straight from Docker Hub, counted against Docker’s limit for the runner’s address. Once it has,
and with runner pull-through on (the default), the runner’s Docker daemon sends Docker Hub pulls
in your jobs through your proxy on that token without any change to the image names, except in
jobs from fork pull requests. See Hosted runners.
Before you start
- The dependency proxy must be on for the organisation. It is available on every plan and on by
default; an owner who turned it off turns it back on under Organisation settings >
Dependency proxy (
PUT /orgs/{org}/dependency-proxywith"enabled": true). - You must be an owner of the organisation to store a credential.
Create a Docker Hub access token
- Sign in to Docker Hub with the account the proxy should use: a service account of your company is better than a person’s.
- Go to Account settings > Personal access tokens and select Generate new token.
- Name it after where it is used, for example
fremforge dependency proxy, and set the access permissions to Public Repo Read-only. The proxy only ever reads. - Copy the token. Docker Hub shows it once.
An organisation access token of a Docker Hub organisation works the same way.
Store it as your organisation’s docker.io upstream credential
In the console: Organisation settings > Dependency proxy > Upstream credentials, under
Add a credential. Choose the registry docker.io, set How it signs in to Username and
password or token, enter the Docker Hub username and the token as the password, and select
Add credential. Test connection has the proxy sign in to Docker Hub with it now.
With the API (a token with policy:write, of an organisation owner):
curl -sS -X POST "https://frem.sh/_app/api/v1/orgs/<org>/dependency-proxy/upstreams" \
-H "Authorization: Bearer $FREMFORGE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"host": "docker.io", "auth_type": "basic", "username": "<docker-hub-user>", "password": "<docker-hub-token>"}'The answer carries the credential’s id and a fingerprint, never the secret. Test it with
POST /orgs/<org>/dependency-proxy/upstreams/<id>/test, turn it off or replace the secret with
PATCH /orgs/<org>/dependency-proxy/upstreams/<id>, and remove it with DELETE.
The secret is write-only: it is sealed when it is stored and is never returned, logged or written to the audit log. The audit log records who added, changed or removed the credential.
Pull through the proxy
Log in to frem.sh with your fremforge user name and a personal access token that has
read:package, then pull with the dependency proxy prefix:
docker login frem.sh -u <your-user>
docker pull frem.sh/<org>/dependency-proxy/docker.io/library/node:24-bookworm-slimOfficial images live under library/ on Docker Hub, so node:24 is
frem.sh/<org>/dependency-proxy/docker.io/library/node:24. Other images keep their namespace:
pgvector/pgvector:pg17 is frem.sh/<org>/dependency-proxy/docker.io/pgvector/pgvector:pg17.
You can pull by digest as well, and a pinned multi-platform digest works unchanged:
FROM frem.sh/<org>/dependency-proxy/docker.io/library/node:24-bookworm-slim@sha256:<digest>In Forgejo Actions
A job can log in with its own job token. It pulls through its own repository’s organisation’s dependency proxy only.
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: log in to the dependency proxy
run: echo "${{ github.token }}" | docker login frem.sh -u "${{ github.actor }}" --password-stdin
- run: docker build -t app .With the Dockerfile’s FROM lines on the frem.sh/<org>/dependency-proxy/docker.io/ prefix,
the build pulls through the proxy with that login.
A job triggered by a pull request from a fork never runs on your organisation’s credentials, so it cannot pull docker.io images through the proxy. Pull those directly from Docker Hub.
Without a credential
If your organisation has no enabled docker.io credential, a pull through the proxy answers
403 DENIED:
docker.io needs your organisation's own Docker Hub credentials (decision D1): an organisation
owner adds them under Organisation settings > Dependency proxy > Upstream credentialsThe proxy does not fall back to an anonymous Docker Hub pull. Either store a credential, or
reference the image directly (docker.io/...), which is an anonymous pull under Docker Hub’s
limit for anonymous pulls.
Other registries behave differently: ghcr.io and quay.io work through the proxy anonymously
when you have no credential for them, and use yours when you do.
Check that it works
- Pull an image through the proxy, then look at your Docker Hub account’s Usage page: the pull is counted there, once. A second pull of the same image is served from the cache.
- If a pull fails with
503and your organisation’s docker.io credential could not be used, the stored credential could not be opened. That is a fremforge fault, and it is logged.