A Docker tag is just a label pointing to an image, and it can be moved. nginx:latest today isn’t the same image as nginx:latest next month. How you choose and create tags decides whether your deployments are reproducible, or full of surprises after an innocent docker compose pull.

Why latest is risky

  • It means “whatever was pushed last”, not “the newest stable version”.
  • A routine pull can jump major versions (PostgreSQL 16 → 17 data directories aren’t compatible).
  • You can’t tell which version is running, or roll back to “the previous latest”.
  • Different servers can end up running different images under the same tag.

Use latest for quick local experiments only.

Pin to a version

Most official images publish several tags for the same image:

Tag Moves when… Use
postgres:16.4 Rebuilds of 16.4 (OS security patches) Production: very predictable
postgres:16 New 16.x minor releases Production: get patch releases automatically
postgres:16-alpine / 16-bookworm Same, with a specific base OS When the base matters
postgres:latest New major versions Avoid

A major-version tag (16) is a good balance for many services: you receive bug and security fixes, and you upgrade majors deliberately. For maximum reproducibility, pin the full version.

Pin by digest (immutable)

A digest identifies the exact image content, and can never change:

docker buildx imagetools inspect nginx:1.27 | grep Digest
services:
  web:
    image: nginx:1.27@sha256:4a5b…   # tag for humans, digest for the engine

Use digests for security-sensitive or regulated deployments, together with a tool that updates them (Renovate, Dependabot), so you still get patches.

Tagging your own images

Give every build a unique, immutable tag, plus optional moving tags:

VERSION=1.8.3
SHA=$(git rev-parse --short HEAD)
docker build -t registry.example.com/myapp:$VERSION \
             -t registry.example.com/myapp:$VERSION-$SHA \
             -t registry.example.com/myapp:1.8 .
docker push --all-tags registry.example.com/myapp
  • Deploy the unique tag (1.8.3, or 1.8.3-a1b2c3d) to production.
  • Moving tags (1.8, main, staging) are fine for convenience and development environments.
  • Never re-push a different image under an existing release tag. Many registries can enforce immutable tags (Harbor, ECR, GHCR settings). See Harbor and Docker private registry.

Keep track of updates

Pinning means updates don’t happen by accident, so make them happen on purpose:

  • Renovate or Dependabot open pull requests when new image versions appear in your Compose or Kubernetes files.
  • Diun or What’s Up Docker notify you about new tags for running containers.
  • Read release notes for major versions, especially databases. See update a Docker container.

Base images in Dockerfiles

The same rules apply to FROM:

FROM python:3.12-slim-bookworm      # not python:latest

Rebuild your images regularly (for example weekly in CI), so they pick up base image security patches, then scan them. See Trivy image scanning and Alpine vs Debian base images.

Quick reference

Situation Tag choice
Production services name:major or name:major.minor.patch
Maximum reproducibility name:version@sha256:…
Your builds Unique version and/or git SHA
Experiments latest is OK
Updates Renovate/Dependabot or notifications, then deliberate deploys

Related: Docker Compose in production and Docker Hub rate limits.

  • Docker
  • DevOps
  • Best Practices