Skip to content

Promote a Docker image between environments

Updated

To promote a Docker image, copy it to the next environment's registry without rebuilding it, and deploy it by digest. A tag can move to other content. A digest can't, so it names exactly what you tested.

Refer to images by digest

A digest pins the content. Any tool that takes an image reference takes one:

docker pull ubuntu@sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef

The same applies to what you deploy. A Kubernetes manifest or a Cloud Run revision that names image@sha256:… runs that content, whatever happens to the tags.

Copy between registries

Keep staging's and production's images in separate repositories or registries, so a staging credential can't touch what production runs. These tools copy an image without rebuilding it:

# crane keeps the digest
crane copy registry.example/staging/api@sha256:… registry.example/prod/api:1.4.2

# skopeo, with --all for every platform of a multi-arch image
skopeo copy --all docker://registry.example/staging/api@sha256:… docker://registry.example/prod/api:1.4.2

# docker buildx, which Docker documents for copying multi-platform images
docker buildx imagetools create --tag registry.example/prod/api:1.4.2 registry.example/staging/api@sha256:…

# hushrun, which can also do this alone, without promotion
hushrun artifact copy registry.example/staging/api@sha256:… registry.example/prod/api:1.4.2

Check the digest after the copy. The image in production should have the digest you tested. Docker's guide shows imagetools in a workflow.

If your images live in JFrog Artifactory, jf rt docker-promote moves one between repositories: see hushrun and JFrog Artifactory.

Prove what you deployed

A digest says what the image is, not how it was made. GitHub's artifact attestations sign that record in the build, with actions/attest, and you can verify it before a deploy:

gh attestation verify oci://registry.example/prod/api@sha256:… --owner acme

GitHub says attestations reach SLSA Build Level 2, and Level 3 with shared reusable workflows. On private repositories they need GitHub Enterprise Cloud.

An attestation records the build. It doesn't record which checks passed on the image, or who approved it: that's the part left to your pipeline.

Decide when to promote

Copying is the mechanical step. A promotion also needs an answer to what has passed on this image, and whether the next environment may take it. The options:

  • By hand: a person reads the results and runs the copy. Fine for a small team and few deploys.
  • In a script in CI: a production workflow copies the image that staging's latest run built. It has to find that image, and to know its checks passed on the same files.
  • With a GitOps tool: Kargo moves new versions through stages, with Argo CD doing the deploy.
  • With hushrun: promotion plans each stage from signed evidence. It copies staging's image by digest and deploys it when the same files passed everything production requires.

With hushrun

hushrun.yaml names each image at every stage:

hushrun.yaml
artifacts:
  image:
    type: oci
    locations:
      staging: us-docker.pkg.dev/acme-staging/api/server
      production: us-docker.pkg.dev/acme-prod/api/server

Before any work, a stage's plan ends in exactly one of six modes: promote, refresh, full, wait, skip or blocked. On promote, the copy and the deploy are all that's left. hushrun verify shows afterwards who signed each event for a digest. Promotion on GitHub Actions has the workflow.

Markdown