Skip to content

Consume container images

Every image is private, so every consumer needs a credential that can read the owner. Use a token scoped to read:package only, so a leaked pull credential can never push or delete. For the registry reference, see Container registry. To publish, see Publish container images.

Sign in as the user or machine account that will pull, and create a personal access token scoped to read:package. For unattended consumers (a server, a cluster, a deploy pipeline), use a dedicated machine user, not a personal account.

Terminal window
echo "$CODEBAHN_TOKEN" | docker login codebahn.net -u "$USERNAME" --password-stdin
docker pull codebahn.net/acme/api:1.4.0

On a server, write the credential once into the Docker config and reuse it. The login stores it under ~/.docker/config.json. Protect that file with file permissions; treat it as a secret.

Create an image pull secret from a read:package token, then reference it in the pod or service account.

Terminal window
kubectl create secret docker-registry codebahn-registry \
--docker-server=codebahn.net \
--docker-username="$USERNAME" \
--docker-password="$CODEBAHN_TOKEN"
apiVersion: v1
kind: Pod
metadata:
name: api
spec:
imagePullSecrets:
- name: codebahn-registry
containers:
- name: api
image: codebahn.net/acme/api:1.4.0

Attach the secret to a service account if every pod in a namespace pulls from the registry, so you do not repeat imagePullSecrets on each workload.

A Codebahn job that deploys or runs an image from its own owner pulls with the built-in ${{ github.token }}, the same credential used to publish. There is no secret to store.

jobs:
deploy:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Log in to the registry
run: echo "${{ github.token }}" | docker login codebahn.net -u "${{ github.actor }}" --password-stdin
- run: docker pull codebahn.net/acme/api:${{ github.ref_name }}

To pull an image owned by a different user or org, the built-in token does not reach across owners. Log in with a read:package machine-user token from a secret instead, and keep that job on protected branches; secrets are withheld from fork pull requests.

- name: Log in to the registry
run: echo "${{ secrets.REGISTRY_PULL_TOKEN }}" | docker login codebahn.net -u "${{ vars.REGISTRY_USER }}" --password-stdin

Pulls from a runner in the same EU region as the registry do not count as internet egress, so in-CI consumption stays cheap.

Pull an immutable tag (:1.4.0 or a commit SHA), never :latest, for anything you deploy. A moving tag means a redeploy can pull a different image than the one you tested, and a rollback cannot find the old one. Resolve to a digest if you want a deploy to fail closed when an image changes:

Terminal window
docker pull codebahn.net/acme/api@sha256:<digest>
  • Use a separate read:package token per consumer, so you can revoke one without breaking the others.
  • Never reuse a write:package token to pull. A pull credential should not be able to push.
  • Rotate machine-user tokens on a schedule, and immediately if a node or config is compromised.