Skip to main content
RunBook Academy

← All checklists in Docker & Containers

Before deploymentDeployment

Checklist: Registry and image-pull readiness

21 items ·11 critical ·7 warn ·3 info

When to run this

Run it in the hour before a production deploy, and again after any change to the registry, its certificate, its credentials, or the daemon’s registry-mirrors and insecure-registries settings. It takes about 25 minutes the first time and under five once the variables are in a file.

How to run it

Every command expects a few variables. Set them once, in the shell you will run the deploy from:

REGISTRY=registry.example.com
REPOSITORY=platform/api
IMAGE="$REGISTRY/$REPOSITORY@sha256:REPLACE_ME"
PREVIOUS_IMAGE="$REGISTRY/$REPOSITORY@sha256:REPLACE_ME"
EXPECTED_IDENTITY='https://github.com/example-org/api/.github/workflows/release.yml@refs/heads/main'
EXPECTED_ISSUER='https://token.actions.githubusercontent.com'

Run every command on the deploy host, as the user the deploy runs as. This is the whole point of the checklist. Docker reads credentials from $HOME/.docker/config.json, so running the credential checks as yourself when the deploy runs as root, or under a systemd unit with a different HOME, verifies a file the deploy will never open. sudo -u deploy -H ... if that is who deploys.

The daemon-configuration items read /etc/docker/daemon.json. On a host with no such file, jq exits non-zero — read that as “no mirror configured” and “no insecure registries configured” respectively, which is a pass for one item and a finding for the other.

Reading the results

Several commands are written so that no output means pass: digest-pinned, no-latest-tag, credentials-not-plaintext and no-insecure-registries all print only the things that are wrong. If one of them prints a line, that line is the finding. Do not run them and skim for something reassuring — there is nothing reassuring to find, by design.

Evidence and sign-off

Paste the output of the critical items into the change record. The two that matter most in a post-incident review are rollback-image-exists and signature-verified: the first is the one teams discover they skipped at the exact moment they need it, and the second is the only one that proves the artefact you are about to run is the artefact your pipeline built.

  • Operator: _________________ Date: ___________
  • Reviewer: ________________ Date: ___________

Critical11 items

  1. getent hosts "$REGISTRY"
  2. curl -sS -o /dev/null -w '%{http_code}\n' "https://$REGISTRY/v2/"
  3. echo | openssl s_client -connect "$REGISTRY:443" 2>/dev/null | openssl x509 -noout -checkend 2592000
  4. openssl s_client -connect "$REGISTRY:443" -showcerts </dev/null 2>/dev/null | grep -c "BEGIN CERTIFICATE"
  5. jq -e --arg r "$REGISTRY" '(.auths[$r] // .credHelpers[$r] // .credsStore) // empty' ~/.docker/config.json
  6. jq -e '(."insecure-registries" // []) | length == 0' /etc/docker/daemon.json
  7. docker manifest inspect "$IMAGE" >/dev/null
  8. docker manifest inspect "$PREVIOUS_IMAGE" >/dev/null
  9. docker ps --format '{{.Names}} {{.Image}}' | grep -v '@sha256:'
  10. docker compose config --images | grep -E ':latest$|^[^:@]+$'
  11. cosign verify --certificate-identity-regexp "$EXPECTED_IDENTITY" --certificate-oidc-issuer "$EXPECTED_ISSUER" "$IMAGE"

Warning7 items

  1. jq -r '(.auths // {}) | to_entries[] | select(.value.auth) | .key' ~/.docker/config.json 2>/dev/null
  2. docker image inspect "$IMAGE" --format '{{index .RepoDigests 0}}'; docker buildx imagetools inspect "$IMAGE" --format '{{.Manifest.Digest}}'
  3. TOKEN=$(curl -s "https://auth.docker.io/token?service=registry.docker.io&scope=repository:ratelimitpreview/test:pull" | jq -r .token); curl -s --head -H "Authorization: Bearer $TOKEN" https://registry-1.docker.io/v2/ratelimitpreview/test/manifests/latest | grep -i ratelimit-remaining
  4. jq -e '(."registry-mirrors" // []) | length > 0' /etc/docker/daemon.json
  5. docker image inspect "$IMAGE" >/dev/null
  6. df -h /var/lib/docker; df -i /var/lib/docker
  7. curl -sS -H "Authorization: Bearer $TOKEN" "https://$REGISTRY/v2/$REPOSITORY/tags/list" | jq -e '.tags | length >= 5'

Info3 items