Docker Image Tagging and Digest-Based Deployment: Best Practices
Learn Docker image tagging strategies, semantic versioning, why latest is dangerous, and how to deploy by digest for reproducible, secure Docker and Kubernetes releases.
Docker Image Tagging, Versioning, and Digest-Based Deployment
"It worked yesterday" is one of the most expensive sentences in operations. Very often the cause is an image reference that silently changed: a server pulled myapp:latest and got a different build than the one that was tested, or a base image tag was updated upstream and a rebuild picked up new, untested packages.
Image tags are human-friendly names, but they are mutable: anyone with push access can point them at different content at any time. Image digests are cryptographic identifiers of the content itself and never change. Using both correctly gives you releases that are readable for people and exact for machines.
This guide explains how tags and digests work, which tagging strategies hold up in production, how to version images, and how to deploy by digest with Docker, Docker Compose, Kubernetes and Amazon ECS. Examples are written for Docker Engine 29, as of October 2026.
Quick Answer
Tag = a mutable name for an image, such as api:1.5.0. Good for humans, not a guarantee of content.
Digest = the SHA-256 hash of an image manifest, such as api@sha256:9c1f.... Immutable: the same digest always means exactly the same image.
Tag every build with an immutable version (for example 1.5.0) and the Git commit (for example sha-3f9c2e1). Optionally add moving convenience tags (1.5, 1, latest) for humans.
Never deploy `latest` to production. Deploy by digest, or by an immutable version tag in a registry that enforces tag immutability.
Build once, promote the same digest through dev, staging and production. Roll back by redeploying the previous digest.
Figure 1: Tags are movable pointers; digests identify exact content and make promotion and rollback reliable
Anatomy of an Image Reference
[registry/][namespace/]repository[:tag][@digest]
123456789012.dkr.ecr.ap-south-1.amazonaws.com/bitcodematrix/api:1.5.0@sha256:9c1f...
registry = 123456789012.dkr.ecr.ap-south-1.amazonaws.com
namespace = bitcodematrix
repository = api
tag = 1.5.0
digest = sha256:9c1f...
Tags are case-sensitive, but repository names must be lowercase, so MyApp:1.0 is rejected while myapp:V1.0 is valid. In practice, keep both lowercase.
Tags: Human-Friendly but Mutable
A tag is just a pointer from a name to an image manifest stored in the registry. Pushing a new image with an existing tag silently moves the pointer:
docker build -t bitcodematrix/api:1.5 . # build A
docker push bitcodematrix/api:1.5
# ... a week later
docker build -t bitcodematrix/api:1.5 . # build B, with new dependencies
docker push bitcodematrix/api:1.5 # 1.5 now points to build B
Every machine that pulls api:1.5 from now on gets build B. Machines that already have build A locally keep running it until they pull again. Two servers running "the same version" can therefore run different code, and nobody can tell from the tag alone.
The Problem With latest
latest is not special in any technical sense. It is simply the tag Docker uses when you do not specify one, so it rarely means "the newest stable release":
It points to whatever was last pushed without an explicit tag, or explicitly as latest, which could be a broken experiment.
It changes constantly, so deployments are not reproducible and rollbacks are guesswork.
In Kubernetes, an image with the latest tag (or no tag) defaults to imagePullPolicy: Always, so every pod restart may pull a different image.
Caches on different hosts hold different versions of latest, causing "works on one node, fails on another" problems.
Rule: Use latest only for local experiments and for documentation convenience. Never reference it in production deployments, Compose files for servers, Kubernetes manifests or Dockerfile FROM lines that must be reproducible.
Digests: Immutable and Verifiable
A digest is the SHA-256 hash of an image's manifest. Because the manifest lists the digests of the configuration and every layer, the image digest effectively fingerprints the entire image. If a single byte of any layer changes, the digest changes.
How to Find an Image's Digest
RepoDigests is populated only for images that were pulled from or pushed to a registry. A freshly built local image has no registry digest until it is pushed.
Pulling and Running by Digest
docker pull bitcodematrix/api@sha256:9c1f...
docker run -d bitcodematrix/api@sha256:9c1f...
# Tag and digest together: readable and exact
docker run -d bitcodematrix/api:1.5.0@sha256:9c1f...
When a reference contains both a tag and a digest, Docker resolves the image by the digest; the tag is kept only for readability. This combined form is a good convention for deployment files, because humans see the version and machines get exact content.
Digests and Multi-Platform Images
A multi-platform image has an index (manifest list) that points to one manifest per platform. It therefore has two kinds of digest:
docker buildx imagetools inspect shows the index digest at the top and each platform's manifest digest below. In most cases, pin the index digest.
Watch out: Copying an image by pulling, re-tagging and pushing can push only the platform you pulled, which produces a different digest in the destination registry. To copy a multi-platform image between registries without changing it, use docker buildx imagetools create --tag <destination> <source> or a dedicated tool such as crane or skopeo.
Choosing a Tagging Strategy
There is no single correct scheme, but production teams converge on a few patterns. Most combine several of them on the same image.
Recommended Default Strategy
For application images built by CI, a practical and widely used scheme is:
Deployments reference only the immutable tags, ideally together with the digest. Moving tags exist purely for people who want "the newest 1.x".
Semantic Versioning for Container Images
Semantic Versioning (SemVer) uses the format MAJOR.MINOR.PATCH:
For container images, "compatibility" includes the image's contract: exposed ports, required environment variables, volumes, the user it runs as and its command-line interface, not just the application code. Changing the default user from root to non-root, for example, can break existing deployments and deserves a major version.
Rebuilding the Same Version
Images are often rebuilt without code changes, for example to pick up security patches in the base image. Options:
Bump the patch version (1.5.1): simplest and fully traceable. This is the recommended approach for your own applications.
Add a build suffix (1.5.0-2 or 1.5.0-20261013): keeps the application version while identifying the rebuild.
Re-push the same tag: what many official images do for moving tags such as 3.13-slim. Acceptable for base images that consumers pin by digest, but avoid it for your release tags.
Recording Version Information in the Image
Tags live in the registry and can change; labels live inside the image configuration and travel with it. Use the standard OCI annotation keys so that tools can read them:
ARG VERSION
ARG REVISION
ARG CREATED
LABEL org.opencontainers.image.title="api" \
org.opencontainers.image.version="${VERSION}" \
org.opencontainers.image.revision="${REVISION}" \
org.opencontainers.image.created="${CREATED}" \
org.opencontainers.image.source="https://github.com/bitcodematrix/api"
docker build \
--build-arg VERSION=1.5.0 \
--build-arg REVISION=$(git rev-parse HEAD) \
--build-arg CREATED=$(date -u +%Y-%m-%dT%H:%M:%SZ) \
-t bitcodematrix/api:1.5.0 .
docker image inspect -f "{{json .Config.Labels}}" bitcodematrix/api:1.5.0
Exposing the version through the application itself, for example in a /version or /health endpoint, makes it trivial to confirm what is actually running.
Building and Tagging in CI
This script builds a multi-platform image once, pushes it with all its tags, and saves the resulting digest for deployment:
#!/usr/bin/env bash
set -euo pipefail
IMAGE=123456789012.dkr.ecr.ap-south-1.amazonaws.com/bitcodematrix/api
VERSION=1.5.0
SHA=$(git rev-parse --short HEAD)
docker buildx build \
--platform linux/amd64,linux/arm64 \
--build-arg VERSION=$VERSION --build-arg REVISION=$(git rev-parse HEAD) \
-t $IMAGE:$VERSION \
-t $IMAGE:$VERSION-$SHA \
-t $IMAGE:sha-$SHA \
--metadata-file build-metadata.json \
--push .
DIGEST=$(jq -r '."containerimage.digest"' build-metadata.json)
echo "Deploy reference: $IMAGE:$VERSION@$DIGEST"
All three tags point to the same digest. The pipeline passes the digest, not just the tag, to the deployment step. In GitHub Actions, the docker/metadata-action action can generate semver, SHA and branch tags automatically from Git events.
Enforce immutability in the registry: Conventions are not enough on their own. Turn on tag immutability where your registry supports it, for example in Amazon ECR (with exclusion filters for moving tags such as latest), Google Artifact Registry, Harbor or Azure Container Registry (tag locking), so that a release tag can never be overwritten. See our tutorial Docker Registry Explained: Docker Hub, Private Registries, and Amazon ECR.
Digest-Based Deployment in Practice
Docker and Docker Compose
services:
api:
image: 123456789012.dkr.ecr.ap-south-1.amazonaws.com/bitcodematrix/api:1.5.0@sha256:9c1f...
restart: unless-stopped
Compose pulls exactly that image on every host. Updating the deployment means changing the reference in the file, which leaves a clear audit trail in version control.
Kubernetes
apiVersion: apps/v1
kind: Deployment
metadata:
name: api
spec:
replicas: 3
selector:
matchLabels:
app: api
template:
metadata:
labels:
app: api
spec:
containers:
- name: api
image: 123456789012.dkr.ecr.ap-south-1.amazonaws.com/bitcodematrix/api:1.5.0@sha256:9c1f...
With a digest (or any tag other than latest), the default imagePullPolicy is IfNotPresent, which is safe because the content behind a digest cannot change. Every pod on every node runs byte-for-byte the same image. With Kustomize, set the digest with kustomize edit set image; with Helm, expose an image.digest value in the chart. GitOps tools such as Argo CD and Flux, with their image automation features, can update digests in Git automatically when a new image is pushed.
Amazon ECS
ECS task definitions accept a digest in the image field. ECS also resolves a tag to its digest when a deployment starts and uses that digest for all tasks in the deployment, so tasks started later in the same deployment run the same image even if the tag moves. Referencing the digest explicitly in the task definition still gives you the clearest audit trail.
Pinning Base Images in Dockerfiles
Pinning matters for inputs too. A Dockerfile that starts with FROM python:3.13-slim can produce a different image every week. Pin the base image by digest for reproducible builds:
FROM python:3.13-slim@sha256:<digest>
Pinned digests do not update themselves, so pair them with automation: Renovate or Dependabot can open pull requests when a newer digest is published for the same tag, giving you security updates through your normal review and test process.
Promotion and Rollback
Build Once, Promote Everywhere
Never rebuild the image for each environment. Build it once, test it, then promote the same digest from dev to staging to production. Rebuilding for production risks picking up different dependencies or base image layers than the ones you tested.
If your environments use separate registries or accounts, copy the image without modification so that the digest stays the same:
docker buildx imagetools create \
--tag prod-registry.example.com/bitcodematrix/api:1.5.0 \
staging-registry.example.com/bitcodematrix/api:1.5.0@sha256:9c1f...
Rolling Back
With digest-based deployment, rollback is precise: redeploy the previous digest recorded in Git or in your deployment tool. There is no doubt about which build you are returning to, and no risk that a tag was overwritten in the meantime. Keep the digests of recent releases, and make sure registry cleanup rules never delete images that are still deployed or needed for rollback.
Security Benefits of Digests
Tamper resistance. If an attacker or a mistaken push overwrites a tag, deployments pinned by digest are unaffected.
Signatures attach to digests. Tools such as Sigstore Cosign and Notation sign a specific digest, so verification proves that exactly that content was approved.
Accurate vulnerability tracking. Scan results and SBOMs belong to a digest; knowing exactly which digest runs where tells you exactly which vulnerabilities you are exposed to.
Audit and compliance. A digest in a deployment record is unambiguous evidence of what was running at a given time.
cosign sign 123456789012.dkr.ecr.ap-south-1.amazonaws.com/bitcodematrix/api@sha256:9c1f...
cosign verify --key cosign.pub 123456789012.dkr.ecr.ap-south-1.amazonaws.com/bitcodematrix/api@sha256:9c1f...
Common Mistakes and How to Fix Them
Frequently Asked Questions
What is the difference between a Docker tag and a digest?
A tag is a human-readable, mutable name that can be moved to different image content at any time. A digest is the SHA-256 hash of the image manifest; it identifies exact content and can never change. Tags are for people; digests are for reproducible deployments.
Why should I not use the latest tag?
Because it is only a default name, not a guarantee of anything. It changes with every untagged push, makes deployments unreproducible and rollbacks unreliable, and in Kubernetes causes images to be pulled on every pod start.
How do I find the digest of a Docker image?
Use docker buildx imagetools inspect <image>:<tag> to read it from the registry, docker image inspect --format "{{index .RepoDigests 0}}" <image> for a pulled image, or read it from the output of docker push.
Can two tags point to the same digest?
Yes, and it is common. A release can be tagged 1.5.0, 1.5, 1 and sha-3f9c2e1 at once; all of them resolve to the same digest.
Does pinning by digest stop me receiving security updates?
It stops unreviewed updates, which is the point. Use tools such as Renovate or Dependabot to propose digest updates automatically, so security patches arrive through tested, reviewed changes.
Should I use semantic versioning or Git SHAs?
Both. Semantic versions communicate meaning to people; Git SHAs give exact traceability to source code. Tag each build with both and deploy using the digest.
Conclusion and Next Steps
Tags and digests solve different problems. Tags give images meaningful names; digests guarantee exactly what runs. A robust release process tags every build with an immutable semantic version and the Git commit, keeps moving tags such as latest for convenience only, enforces tag immutability in the registry, records version labels inside the image, and deploys and promotes images by digest. The result is a pipeline where what you tested is exactly what you ship, and where rolling back is as simple as redeploying a known digest.
Continue the Docker course with these tutorials on BitCodeMatrix:
Docker Registry Explained: Docker Hub, Private Registries, and Amazon ECR — tag immutability, lifecycle rules and pull-through caches
Docker Image Layers, Union Filesystems, and Image Caching Explained — what a digest is actually hashing
Docker Architecture Explained — how the daemon resolves image references
Image Signing With Cosign (next in the course) — prove that a digest was built and approved by you