Skip to main content

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:

RouteImage referenceCredentialLimits
Through your organisation’s dependency proxyfrem.sh/<org>/dependency-proxy/docker.io/<namespace>/<repository>:<tag>Your organisation’s Docker Hub token, stored onceYour Docker Hub account’s limits. A cached image does not count against Docker Hub again.
Directly from Docker Hubdocker.io/<namespace>/<repository>:<tag>None (anonymous), or your own docker loginDocker 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-proxy with "enabled": true).
  • You must be an owner of the organisation to store a credential.

Create a Docker Hub access token

  1. Sign in to Docker Hub with the account the proxy should use: a service account of your company is better than a person’s.
  2. Go to Account settings > Personal access tokens and select Generate new token.
  3. 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.
  4. 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-slim

Official 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 credentials

The 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 503 and 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.