Using the latest tag in Docker is like saying “whatever version I want to use today.” It’s convenient, it sounds safe, and it’ll bite you hard in production.
Here’s the thing: latest is just a string. It doesn’t mean “newest version.” It’s a tag that the image builder decides to update (or not). It doesn’t auto-update. And when things break, you have no idea which version broke.
What latest Actually Is
latest is a Docker image tag—just a pointer to a specific layer. It’s not special. It’s whatever the image maintainer decided to tag as latest. Here’s an example:
# Build and tag v1.0.0$ docker build -t myapp:v1.0.0 .$ docker tag myapp:v1.0.0 myapp:latest
# Build and tag v1.0.1$ docker build -t myapp:v1.0.1 .$ docker tag myapp:v1.0.1 myapp:latest # ← latest now points to v1.0.1That’s it. latest is just a tag that gets reassigned. The maintainer could forget to update it. Or deliberately keep it pointing to v1.0.0 forever. You don’t know.
The Problems
Problem 1: Reproducibility
You deploy docker run myapp:latest on Monday. It works. On Friday, you deploy it again. But latest now points to a different image, and your app breaks. You have no idea what version you’re running or what changed.
# Monday$ docker run myapp:latestlatest: Pulling from library/myappDigest: sha256:abc123...
# Friday$ docker run myapp:latestlatest: Pulling from library/myappDigest: sha256:def456... # ← Different image, no warningDifferent digest, same tag. Silent failure.
Problem 2: Build Caching
If you use latest in a Dockerfile:
FROM myapp:latestRUN ...Docker can’t know if the base image changed. Layer caching gets confused. You might get old or new versions unpredictably.
Problem 3: Debugging
When something breaks in production, you log into the container:
$ docker exec myapp cat /app/VERSIONERROR: No version file
$ docker inspect myapp:latest | grep -i version(nothing)Which version is this? No idea. You’d have to check the digest and hunt through your image registry to figure out what was deployed. That’s not debugging, that’s forensics.
The Solution: Pin Versions
Pin specific, immutable tags:
# Good: specific version$ docker run myapp:v1.2.3
# Better: version + digest$ docker run myapp:v1.2.3@sha256:abc123def456...Dockerfile:
# BadFROM node:latest
# GoodFROM node:18.19.0
# BetterFROM node:18.19.0@sha256:abc123...With pinned versions, rebuilds are reproducible. You know exactly what you’re running.
How to Get the Digest
$ docker pull myapp:v1.2.3v1.2.3: Pulling from myappDigest: sha256:abc123def456ghi789... # ← Use this in your DockerfileStatus: Downloaded newer image for myapp:v1.2.3
# Or inspect locally$ docker inspect myapp:v1.2.3 | grep RepoDigests -A 1"RepoDigests": [ "myapp@sha256:abc123def456ghi789..."]Then use it:
FROM myapp:v1.2.3@sha256:abc123def456ghi789...The Semantic Versioning Pattern
Use semantic versioning for your images. Tag each build with major.minor.patch:
# Build and tag$ docker build -t myapp:1.2.3 .
# Optionally also tag the major and minor for convenience$ docker tag myapp:1.2.3 myapp:1.2$ docker tag myapp:1.2.3 myapp:1
# Never tag latest unless you're sure# (Or document that it's updated on every release)Now users can:
- Use
myapp:1.2.3for exact reproducibility - Use
myapp:1.2to get the latest patch for v1.2 (security patches) - Use
myapp:1to get the latest v1 (breaking changes are v2)
Real Example: Compose File
Here’s a production-ready compose file:
version: '3.8'
services: db: image: postgres:15.2 # Explicit version. If there's a bug in 15.3, you stay on 15.2
redis: image: redis:7.2.1@sha256:abc123... # Pinned with digest. Even more explicit
api: build: context: . dockerfile: Dockerfile # Built locally, version comes from git tag or build metadata
frontend: image: mycompany/frontend:v2.1.4 # Tagged with version. You know exactly what you're deployingWhen latest Is Actually Okay
There are a few cases where latest makes sense:
-
Development only: If you’re in a dev environment and rebuilding constantly,
latestis fine. You’re testing changes. -
Stable release policies: Some projects (like Ubuntu, Alpine) tag
latestonly when releasing to stable channel. Check their release docs. -
Internal only: If you control both the builder and the consumer,
latestcan work if you document its policy.
But for production? No. Always pin versions.
Checklist for Production
- No
latesttag in production Dockerfiles or compose files - No
latesttag in container run commands - Use semantic versioning:
v1.2.3,v2.0.0, etc. - Optionally pin digests for even more safety:
image:v1.2.3@sha256:... - Document your versioning policy (when you tag
v1,v1.2, etc.) - Update images explicitly, don’t rely on auto-pull of
latest - Test a new version in staging before promoting to production
Version pinning is boring. It’s also the difference between predictable deployments and “why is production broken?” at 2 AM.
Use latest in development. Use versions in production.