How to Pin Docker Images and Avoid Bad Updates
Keep your self-hosted stack more predictable by pinning Docker image versions, testing updates deliberately, and making rollbacks less painful.
How tags, digests, and Compose files affect update behavior, and how to avoid waking up to a broken app after an unplanned image change.
Docker Compose operators who want safer updates than latest and a better path back when a vendor ships a bad release.
Unpinned images can change under the same tag, which means yesterday's working deploy might not be reproducible today.
Before you begin
- A Docker Compose stack already running on your VPS or server.
- Access to the current
docker-compose.ymlorcompose.ymlfile. - A recent backup of the app data if the service is stateful.
Many self-hosting problems do not come from Docker itself. They come from update habits. If your Compose file says image: vendor/app:latest, you are trusting whatever that tag points to whenever you next pull or rebuild. That may work for a while, until it does not.
Step 1: Understand what pinning actually means
There are three common patterns:
- Floating tags, such as
latestorstable. Convenient, but risky. - Version tags, such as
1.27.3. Better, because you know what you asked for. - Digests, such as
vendor/app@sha256:.... Most exact, because the image content is fixed.
For most beginners, a version tag is the right starting point. Digests are even safer for reproducibility, but they are more verbose. The big lesson is simple: do not let production depend on vague image names if you care about stable behavior.
Step 2: Inspect what you are running now
List current images:
docker compose imagesInspect the exact image currently in use:
docker inspect --format '{{.Config.Image}}' myapp-containerIf you see latest or another floating tag, check the vendor documentation or registry page for explicit versions. For example, change this:
image: ghost:latestTo this:
image: ghost:5.118.1Or this:
image: linuxserver/wikijs:version-2.5.306When possible, read the release notes before choosing the target version. Pinning is about controlled updates, not just freezing forever.
Step 3: Update your Compose file safely
Open your Compose file and replace floating tags with explicit versions. Example:
services:
app:
image: ghcr.io/example/selfhosted-app:1.14.2
restart: unless-stopped
env_file:
- .env
volumes:
- app_data:/var/lib/app
ports:
- "127.0.0.1:3000:3000"
volumes:
app_data:If you want maximum repeatability, pin by digest after pulling the version you chose:
docker pull ghcr.io/example/selfhosted-app:1.14.2
docker image inspect ghcr.io/example/selfhosted-app:1.14.2 --format '{{index .RepoDigests 0}}'Then use the digest in Compose:
image: ghcr.io/example/selfhosted-app@sha256:abcdef123456...Keep a short comment nearby or in your deployment notes explaining why a version was chosen. That saves time later when you revisit the file.
Step 4: Test the update before trusting it
Pull the pinned image deliberately:
docker compose pullReview what will change:
docker compose imagesRestart the stack:
docker compose up -dThen verify the app from both inside and outside:
docker compose ps
docker compose logs --tail=100
curl -I https://example.comFor more cautious operators, test the new image in staging or on a secondary VPS first. Even a quick test environment is better than discovering a breaking change on production traffic.
Expected outcomes
- Your Compose file names exact image versions or digests.
- Future deploys are more reproducible.
- You can tell which version is running without guessing.
- Rollback is faster because the previous version is known.
Rollback and recovery notes
If the new image misbehaves, edit the Compose file back to the previous known good tag or digest:
docker compose pull
docker compose up -dIf the app changed data formats during startup, a rollback may require restoring data too. That is why stateful app updates should happen only after a backup or snapshot. Pinning reduces risk, but it does not remove the need for recovery planning.
A good habit is to keep a short changelog in your repo or deployment notes that records:
- Old version
- New version
- Date changed
- Why the update was made
- Whether any migration happened
Troubleshooting common image update issues
The service still pulled a different image.
Double-check that every Compose file override uses the same image reference and that no automation rewrites it later.
The pinned tag does not exist anymore.
Some vendors clean up old tags. Use an available supported version and consider pinning by digest for critical workloads.
The container starts, but the app fails.
Read the release notes and container logs. The issue may be a config change, migration step, or a deprecation in environment variables.
The rollback tag starts, but data errors remain.
The failed upgrade may have changed persistent data. Restore from backup if needed.
What to do next
Once image updates are predictable, the next safety layer is stronger backup coverage for persistent data. Continue with How to Back Up Docker Volumes to MinIO with Restic.
