# Promote a Docker image between environments

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, where a tag can move: [Docker image digest vs tag](https://hushrun.com/docs/guides/docker-image-digest-vs-tag.md) explains the difference and how to find one. Any tool that takes an image reference takes one:

```bash
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:

```bash
# 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](https://docs.docker.com/build/ci/github-actions/copy-image-registries/) 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](https://hushrun.com/docs/compare/artifactory.md).

## Prove what you deployed

A digest says what the image is, not how it was made. [GitHub's artifact attestations](https://docs.github.com/en/actions/concepts/security/artifact-attestations) sign that record in the build, with `actions/attest`, and you can verify it before a deploy:

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

GitHub says attestations reach [SLSA](https://slsa.dev/spec/v1.2/build-track-basics) 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](https://hushrun.com/docs/compare/kargo.md) moves new versions through stages, with Argo CD doing the deploy.
- **With hushrun:** [promotion](https://hushrun.com/docs/promotion.md) 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:

```yaml title="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](https://hushrun.com/docs/promotion/github.md) has the workflow.

## Related

- [Build once, deploy many](https://hushrun.com/docs/guides/build-once-deploy-many.md)
- [Promotion](https://hushrun.com/docs/promotion.md)
