Chainguard
Auth
cgr.dev uses the standard OCI Bearer token exchange. Three credential paths, in order of likely use:
auth_type: basicwith a chainctl pull-token (recommended for CI / Kubernetes).chainctl auth pull-tokenissues a long-lived(username, password)pair scoped to an organization or library. Drop those values intocredentials.username/credentials.password; no docker config file required.auth_type: docker_config(or unset, which auto-detects) withchainctl auth login(recommended for developer machines).chainctl auth configure-dockerwrites credentials into~/.docker/config.json.- Anonymous (free tier). Limited to
:latestand:latest-devon the public catalog. Version-pinned tags, historical revisions, and the full FIPS catalog require a pull-token.
See the Chainguard auth docs for issuing pull-tokens and configuring chainctl.
Notable behaviors:
- cgr.dev returns 403 on cross-scope token reuse where most registries silently accept it. ocync’s per-scope token cache (universal, not Chainguard-specific) issues a fresh exchange per
repository:<name>:<actions>scope and avoids this. - Anonymous tag visibility is restricted: only
:latestand:latest-devare listable / resolvable. Other tags need a pull-token even for public-catalog images.
CLI examples
Anonymous
Free tier, limited to :latest and :latest-dev.
ocync copy \
cgr.dev/chainguard/static:latest \
ghcr.io/myorg/static:latest
Pull-token
Recommended for CI. Generate a pull-token once; the output includes a username and password to capture into your secret store.
chainctl auth pull-token --library-paid <your-org-slug>
# Username: <pull-token-id>
# Password: <pull-token-secret>
Then either use the credentials directly on the CLI:
CHAINGUARD_USERNAME=<pull-token-id> \
CHAINGUARD_PASSWORD=<pull-token-secret> \
ocync sync --config ocync.yaml
with ocync.yaml:
registries:
src:
url: cgr.dev
auth_type: basic
credentials:
username: ${CHAINGUARD_USERNAME}
password: ${CHAINGUARD_PASSWORD}
ghcr:
url: ghcr.io
auth_type: static_token
token: ${GITHUB_TOKEN}
defaults:
source: src
targets: ghcr
mappings:
- from: chainguard/static
to: myorg/static
- from: chainguard/python
to: myorg/python
Developer machine
chainctl auth login + chainctl auth configure-docker writes credentials to ~/.docker/config.json; ocync picks them up automatically.
chainctl auth login
chainctl auth configure-docker
# ocync picks up the credentials automatically:
ocync copy \
cgr.dev/chainguard/python:3.12 \
ghcr.io/myorg/python:3.12
Kubernetes deployment
The recommended K8s path is the same auth_type: basic flow as CI, with the pull-token’s username/password injected via envFrom:
# values.yaml
config:
registries:
src:
url: cgr.dev
auth_type: basic
credentials:
username: ${CHAINGUARD_USERNAME}
password: ${CHAINGUARD_PASSWORD}
ghcr:
url: ghcr.io
auth_type: static_token
token: ${GITHUB_TOKEN}
defaults:
source: src
targets: ghcr
mappings:
- from: chainguard/static
to: myorg/static
envFrom:
- secretRef:
name: ocync-credentials
Create the Secret out-of-band, capturing the pull-token output:
chainctl auth pull-token --library-paid <your-org-slug>
# Username: <pull-token-id>
# Password: <pull-token-secret>
kubectl create secret generic ocync-credentials \
--from-literal=CHAINGUARD_USERNAME=<pull-token-id> \
--from-literal=CHAINGUARD_PASSWORD=<pull-token-secret> \
--from-literal=GITHUB_TOKEN="$(gh auth token)"
For credential rotation — pulling the pull-token out of AWS Secrets Manager / GCP Secret Manager / Azure Key Vault / Vault and refreshing it on a schedule — use the External Secrets Operator or CSI Secrets Store patterns documented in Kubernetes secret patterns; the auth_type: basic configuration above stays unchanged, only the source of CHAINGUARD_USERNAME / CHAINGUARD_PASSWORD changes.
Docker config volume
For a lift-and-shift from a machine that already has ~/.docker/config.json populated by chainctl auth configure-docker, mount a Secret-backed docker config and use auth_type: docker_config:
# values.yaml
config:
registries:
src: { url: cgr.dev, auth_type: docker_config }
# ... targets ...
extraVolumes:
- name: docker-config
secret:
secretName: chainguard-docker-config
items:
- key: config.json
path: config.json
extraVolumeMounts:
- name: docker-config
mountPath: /home/nonroot/.docker
readOnly: true
chainctl auth login
chainctl auth configure-docker
kubectl create secret generic chainguard-docker-config \
--from-file=config.json=$HOME/.docker/config.json
This works but ties the pod to a specific ~/.docker/config.json snapshot; rotating the credentials means re-running chainctl auth configure-docker and re-creating the Secret. The auth_type: basic + envFrom path is more declarative and rotates cleanly via ESO / CSI.