Image Promotion: Moving Docker Images Safely Between Registries and Environments
AI generated
FROM
RUN
Docker · Registry · CI/CD
Image Promotion
moving container images safely between stages

Image promotion describes how an already built Docker image moves in a controlled way from staging to production, without being rebuilt. Retagging, digest pinning and manual approvals replace the fragile reflex of simply rebuilding on every deploy and hoping nothing changed.

17 min read Retagging · digest pinning · promotion gates GitLab CI · GitHub Actions · registries

1. What sets image promotion apart from a simple deploy

Image promotion describes the controlled process of moving an already built and tested Docker image from one environment to the next, typically from staging to production, without rebuilding it. The difference from a simple deploy lies in the safeguards: image promotion usually includes approval steps, proof of passed tests, and an immutable reference to exactly the artifact being promoted. A plain deploy command without these controls can accidentally roll out a different image than the one actually tested.

The reason image promotion deserves to be treated as its own concept is the role of the container registry as the source of truth. Unlike classic artifact repositories for JAR or ZIP files, a registry stores images under tags that are mutable by default. A tag such as staging can point to a different image at any time. Image promotion therefore has to explicitly ensure that the reference being promoted stays stable and unambiguous, usually through a digest or an immutable version tag.

In grown Docker setups, image promotion is often seen happening informally through Slack messages or manual SSH commands. That works for a while, but breaks down under staff turnover or time pressure. A structured promotion workflow with clear gates in the CI pipeline makes this process repeatable, auditable, and independent of individual people who happen to know the exact procedure.

2. Retagging instead of rebuilding: the technical foundation

The technical foundation of image promotion is retagging: instead of rebuilding an image for the target environment, the same image content is simply given a new tag and pushed to the registry, or referenced directly by the same digest. docker tag creates no new layer and no new bytes, it only adds an additional name for the same image ID. That exact property is what makes image promotion safe: there is no way for code to change during retagging.

Tools such as crane or skopeo go one step further than native docker tag for image promotion, because they can copy images directly between registries without downloading and re-uploading them locally. That matters especially when the target registry lives in a different network or with a different cloud provider. For plain retagging within the same registry, a simple docker pull, docker tag, docker push sequence is usually enough.


#!/usr/bin/env bash
# promote.sh -- promote an existing image to a new stage tag, no rebuild
set -euo pipefail

SOURCE_TAG="${1:?Usage: promote.sh <source-tag> <target-tag>}"
TARGET_TAG="${2:?Usage: promote.sh <source-tag> <target-tag>}"
IMAGE="registry.example.com/myapp"

echo "[promote] Pulling ${IMAGE}:${SOURCE_TAG}"
docker pull "${IMAGE}:${SOURCE_TAG}"

# Retag -- zero new bytes, same digest, only a new reference
docker tag "${IMAGE}:${SOURCE_TAG}" "${IMAGE}:${TARGET_TAG}"
docker push "${IMAGE}:${TARGET_TAG}"

echo "[promote] ${SOURCE_TAG} -> ${TARGET_TAG} done, same content"

# Cross-registry copy without a local docker daemon (using crane)
# crane copy registry-a.example.com/myapp:1.4.2 registry-b.example.com/myapp:1.4.2

3. Digest pinning: tags are mutable, digests are not

A central problem with image promotion: tags in Docker registries are mutable by definition. Nothing stops a second build from pushing the same tag again and overwriting the previous content. If a deploy configuration only references the tag, the content behind that tag can change unnoticed between approval and actual deploy. That exact problem is what digest pinning solves: every image has, in addition to its tag, a cryptographic digest (sha256:...) derived from its content, which is guaranteed to be immutable.

For safe image promotion, the deploy reference should therefore contain not just the tag but the full digest. A deployment referencing myapp@sha256:a1b2c3... instead of myapp:staging can no longer be changed by later overwriting the tag. Many registries and orchestration tools support this pattern natively, Kubernetes for instance resolves image references internally at the digest level anyway once imagePullPolicy: Always is used together with a digest.


# Resolve the digest for a given tag before promotion
DIGEST=$(docker inspect --format='{{index .RepoDigests 0}}' registry.example.com/myapp:1.4.2)
echo "Resolved digest: ${DIGEST}"
# registry.example.com/myapp@sha256:a1b2c3d4e5f6...

# Deploy by digest, not by mutable tag -- guarantees the exact same bytes
kubectl set image deployment/app app="${DIGEST}" -n production

# Verify the digest matches the one validated in staging before promoting
STAGING_DIGEST=$(kubectl get deployment app -n staging \
  -o jsonpath='{.spec.template.spec.containers[0].image}')
if [[ "${STAGING_DIGEST}" != "${DIGEST}" ]]; then
  echo "[ERROR] Digest mismatch -- refusing to promote" >&2
  exit 1
fi

4. Promotion between multiple registries

Not every setup has just one registry. Larger organizations often run separate registries per environment, for example an internal development registry and a more strictly secured production registry with restricted access. Image promotion between registries then means explicitly pulling the image from the source registry and copying it to the target registry, while the digest and content must stay identical, only the storage location and possibly the repository path change.

For this kind of image promotion, it is important that the copy operation itself runs through a dedicated CI job with restricted, auditable credentials, not through an individual developer with personal access to the production registry. Tools such as crane copy or regctl image copy transfer manifest and layers directly between registries, without a local cache, which is both faster and reduces the attack surface.

5. Promotion gates: manual approvals and automated checks

A promotion gate is the checkpoint at which image promotion either proceeds automatically or waits for an explicit approval. In GitLab CI this is modeled through when: manual combined with environment blocks, in GitHub Actions through environment protection rules with required reviewers. Both mechanisms ensure that promotion to production does not happen automatically on every green pipeline run, but requires a deliberate human decision, at least for the most critical environment.

Automated gates usefully complement manual approval for image promotion: a security scan that blocks critical vulnerabilities, a smoke test against the staging instance, or a check whether all database migrations have already been applied. Only once all automated gates are green is the manual approval even offered, instead of confronting people with checks that are obviously failing.


# .gitlab-ci.yml -- promotion gate: automated checks, then manual approval
promote-to-production:
  stage: promote
  environment:
    name: production
    url: https://shop.example.com
  when: manual
  needs:
    - job: security-scan
      artifacts: false
    - job: smoke-test-staging
  script:
    - echo "Promoting ${CI_COMMIT_SHA} to production"
    - crane copy
        registry.example.com/myapp:${CI_COMMIT_SHA}
        registry.example.com/myapp:production
  rules:
    - if: '$CI_COMMIT_BRANCH == "main"'

6. A complete promotion pipeline in GitLab CI

A mature image promotion pipeline consists of several sequential stages: build, automated tests against the built image, deploy to staging, automated and manual approval, and finally promotion to production through retagging or a digest reference. Every stage references the same immutable artifact created in the first step, and only adds further proof that this artifact is ready for the next stage.

When implementing it, the image promotion pipeline should always log in a traceable way who approved what and when. GitLab and GitHub store this information by default in the deployment history, which is indispensable during audits or incident reviews. In addition, a Slack or email hook can send a notification to the whole team on every production promotion, so nobody is caught off guard by a production deploy.

7. Traceability: which image ran when and where

An often underestimated benefit of consistent image promotion is traceability after the fact. If every promotion is logged through the registry API or the CI system, it becomes possible at any time to reconstruct which commit ran in which environment at which point in time. That is invaluable during incident analysis: instead of guessing whether a bug was caused by a recent deploy, the promotion history shows exactly when which artifact arrived in production.

For image promotion, it is advisable to set labels on the image itself, such as org.opencontainers.image.revision with the Git commit hash and org.opencontainers.image.created with the build timestamp. These OCI standard labels can be read at any time with docker inspect from a running container, regardless of which deployment tool performed the promotion.

8. Common mistakes with image promotion

The most common mistake with image promotion is using the latest tag as the promotion target. Since latest gets overwritten by convention time and again, any promotion based on it loses all traceability. A second common mistake: the staging environment is deployed with one tag, but the production promotion accidentally references a different, similarly named tag because a pipeline script passes the variable through incorrectly.


# WRONG: promoting via a mutable, ambiguous tag
docker pull registry.example.com/myapp:latest
docker tag registry.example.com/myapp:latest registry.example.com/myapp:production
# "latest" could be anything by the time this runs

# RIGHT: promote by the exact digest validated in staging
STAGING_DIGEST="sha256:a1b2c3d4e5f6..."
docker pull "registry.example.com/myapp@${STAGING_DIGEST}"
docker tag "registry.example.com/myapp@${STAGING_DIGEST}" registry.example.com/myapp:production
docker push registry.example.com/myapp:production

A third mistake is anchoring promotion gates purely in the team's process knowledge, without technically enforcing them. If any developer with sufficient registry rights can theoretically overwrite a tag manually, the gate is meaningless no matter how well the pipeline is documented. Access rights to the production registry should therefore be strictly limited to the CI service accounts that perform the promotion.

9. Promotion strategies compared side by side

The following table compares common approaches to image promotion, from the unsafe manual variant to the fully automated digest based pipeline.

Approach Unsafe Recommended image promotion Benefit
Deploy reference myapp:latest myapp@sha256:... Immutable, exactly reproducible
Staging to prod transition New build for production Retag / digest copy No risk of divergent artifacts
Approval Manual SSH access Promotion gate in CI Auditable, tied to a person
Registry access Personal credentials Dedicated CI service account Restricted, logged rights
Traceability Only known informally OCI labels + deployment log Reconstructible at any time

The effort of a cleanly automated image promotion pipeline pays off especially during incidents. A team that can say within seconds which commit is currently running in production and when it was promoted there noticeably shortens the mean time to resolution compared to a team that first has to research manually.

Mironsoft

Docker registries, promotion pipelines and container security

Setting up trustworthy image promotion?

We set up promotion pipelines with digest pinning, automated gates and logged approvals, so every promotion from staging to production stays traceable and safe.

Promotion pipeline

Retagging, digest copy and gates in GitLab CI or GitHub Actions

Registry hardening

Restricting access rights to CI service accounts, securing tags

Audit trail

OCI labels and deployment logs for complete traceability

10. Summary

Image promotion solves a concrete trust problem in container pipelines: how to ensure an image tested in staging lands bit for bit identical in production. Retagging instead of rebuilding, digest pinning instead of mutable tags, and promotion gates with automated checks plus logged manual approvals together form a process that is both safe and traceable.

Anyone who handles image promotion consistently through digests rather than tags, and restricts access to production registries to dedicated CI service accounts, eliminates an entire class of bugs caused by accidentally overwritten tags or unclear approval processes. The investment in a clean promotion pipeline pays off especially during incidents, when fast, unambiguous answers about the current state of production are needed.

Image promotion — the essentials at a glance

Retag instead of rebuild

docker tag or crane copy create no new bytes, only an additional reference to the same image.

Digest pinning

Deploy references should contain the digest, not just the mutable tag, for guaranteed reproducibility.

Promotion gates

Enforce automated checks and manual approvals in the CI pipeline, not just document them.

Traceability

OCI labels and deployment logs make it reconstructible at any time which artifact ran when and where.

11. FAQ: Image Promotion with Docker

1What does image promotion actually mean?
A tested image is moved to the next environment in a controlled way, without being rebuilt.
2Why isn't simple retagging always enough?
Tags are mutable. Without digest pinning content can change unnoticed.
3Difference between a tag and a digest?
Tag is mutable and readable, digest is an immutable hash of the content.
4How does a promotion gate work in GitLab CI?
Through when: manual in an environment block, combined with automated checks as a precondition.
5Tag or digest in deploy configurations?
Digest for production, guaranteed immutable, the tag serves only as readable extra info.
6How do you copy images between registries?
With crane copy or regctl image copy, directly without a local Docker daemon.
7Who should have registry access?
Only dedicated CI service accounts with restricted, logged rights.
8Which labels help with traceability?
OCI labels like image.revision and image.created, readable with docker inspect.
9Most common mistake with image promotion?
Using the latest tag as the promotion target, losing all traceability.
10Does every environment need its own registry?
No, often one registry with different tags or namespaces per environment is enough.