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.
Table of Contents
- 1. What sets image promotion apart from a simple deploy
- 2. Retagging instead of rebuilding: the technical foundation
- 3. Digest pinning: tags are mutable, digests are not
- 4. Promotion between multiple registries
- 5. Promotion gates: manual approvals and automated checks
- 6. A complete promotion pipeline in GitLab CI
- 7. Traceability: which image ran when and where
- 8. Common mistakes with image promotion
- 9. Promotion strategies compared side by side
- 10. Summary
- 11. FAQ
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.