Skip to content
Go back

Why the `latest` Docker Tag Is Lying to You

By SumGuy 5 min read
Why the `latest` Docker Tag Is Lying to You

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:

Terminal window
# 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.1

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

Terminal window
# Monday
$ docker run myapp:latest
latest: Pulling from library/myapp
Digest: sha256:abc123...
# Friday
$ docker run myapp:latest
latest: Pulling from library/myapp
Digest: sha256:def456... # ← Different image, no warning

Different digest, same tag. Silent failure.

Problem 2: Build Caching

If you use latest in a Dockerfile:

Dockerfile
FROM myapp:latest
RUN ...

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:

Terminal window
$ docker exec myapp cat /app/VERSION
ERROR: 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:

Terminal window
# Good: specific version
$ docker run myapp:v1.2.3
# Better: version + digest
$ docker run myapp:v1.2.3@sha256:abc123def456...

Dockerfile:

Dockerfile
# Bad
FROM node:latest
# Good
FROM node:18.19.0
# Better
FROM node:18.19.0@sha256:abc123...

With pinned versions, rebuilds are reproducible. You know exactly what you’re running.

How to Get the Digest

Terminal window
$ docker pull myapp:v1.2.3
v1.2.3: Pulling from myapp
Digest: sha256:abc123def456ghi789... # ← Use this in your Dockerfile
Status: 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:

Dockerfile
FROM myapp:v1.2.3@sha256:abc123def456ghi789...

The Semantic Versioning Pattern

Use semantic versioning for your images. Tag each build with major.minor.patch:

Terminal window
# 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:

Real Example: Compose File

Here’s a production-ready compose file:

docker-compose.yml
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 deploying

When latest Is Actually Okay

There are a few cases where latest makes sense:

  1. Development only: If you’re in a dev environment and rebuilding constantly, latest is fine. You’re testing changes.

  2. Stable release policies: Some projects (like Ubuntu, Alpine) tag latest only when releasing to stable channel. Check their release docs.

  3. Internal only: If you control both the builder and the consumer, latest can work if you document its policy.

But for production? No. Always pin versions.

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


Share this post on:

Send a Webmention

Written about this post on your own site? Send a webmention and it may appear here.


Previous Post
The SSH Config File: The Shortcut You're Not Using
Next Post
Why kill -9 Is the Wrong Default

Related Posts