docker

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.

By Shubhankar Tripathi • • 5 min read

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

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...

Part

Rules

Default

Registry

Host name, optionally with a port, such as ghcr.io or registry.example.com:5000

docker.io (Docker Hub)

Namespace and repository

Lowercase letters, digits and separators (., _, __, -), split into path components with /

library namespace for single-word Docker Hub names

Tag

Up to 128 characters: letters, digits, _, . and -; cannot start with . or -

latest

Digest

Algorithm and hex hash, for example sha256: followed by 64 hexadecimal characters

None


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

Method

Command

From the push output

docker push ends with a line such as 1.5.0: digest: sha256:9c1f... size: 1789

From a local image

docker image inspect --format "{{index .RepoDigests 0}}" bitcodematrix/api:1.5.0

List local images with digests

docker image ls --digests bitcodematrix/api

From the registry, without pulling

docker buildx imagetools inspect bitcodematrix/api:1.5.0

At build time, in a file

docker buildx build --push --metadata-file meta.json ... then read containerimage.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:

Digest

Identifies

When to use it

Index digest

The multi-platform image as a whole

Deployments that may run on more than one architecture; the runtime selects the right platform automatically

Platform manifest digest

One platform's image, for example linux/arm64

Pinning a specific platform intentionally


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.

Strategy

Example tags

Pros

Cons

Semantic version

1.5.0

Meaningful to humans; signals the type of change

Requires a release process

Git commit SHA

sha-3f9c2e1

Unique per commit; traceable to exact source

Not human-friendly on its own

Version + build metadata

1.5.0-3f9c2e1

Readable and traceable in one tag

Longer tags

Moving semver tags

1.5, 1

Users can follow patch or minor updates

Mutable; not for production pinning

Branch or environment

main, develop, pr-482

Handy for previews and testing

Mutable; never use as a release identifier

Date-based

2026.10.13

Natural for scheduled releases and base images

Says nothing about compatibility

latest

latest

Convenient for quick local use

Unpredictable; avoid in deployments


For application images built by CI, a practical and widely used scheme is:

When

Tags to push

Mutable?

Every commit on the main branch

sha-<short-commit>, and optionally main

SHA: no; main: yes

Pull request builds

pr-<number>

Yes (preview only)

Release v1.5.0

1.5.0, 1.5.0-<short-commit>, plus 1.5, 1 and optionally latest

1.5.0: no; 1.5, 1, latest: yes


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:

Part

Increase when

Example

MAJOR

You make incompatible changes, such as a breaking API change, removed environment variables or a new required volume

1.5.0 to 2.0.0

MINOR

You add functionality in a backwards-compatible way

1.5.0 to 1.6.0

PATCH

You make backwards-compatible bug fixes

1.5.0 to 1.5.1


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

Mistake

Consequence

Fix

Deploying latest

Unpredictable, non-reproducible deployments

Deploy version tags with digests

Reusing release tags

Same version name, different content

Make release tags immutable in the registry

Rebuilding per environment

Production runs untested code

Build once and promote the same digest

Unpinned FROM lines

Builds change without any code change

Pin base images by digest and update them with Renovate or Dependabot

Pull, re-tag, push to copy images

Platforms lost, digest changes

Use docker buildx imagetools create, crane or skopeo

Pinning a single-platform digest by accident

exec format error on other architectures

Pin the index digest of multi-platform images

Aggressive registry cleanup

Rollback target deleted

Exclude deployed and recent release images from lifecycle rules

Version only in the tag

Cannot tell what a running container is

Add OCI labels and expose a version endpoint


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