Common Gotchas
Everything in this lesson is a mistake that practically every Docker user makes exactly once. Read it now and you get to skip the "exactly once" part.
💡 The Bottom Line
Most Docker surprises trace back to three habits worth building early: check
docker logs before anything else, remember that container filesystems are
disposable, and treat image size and freshness as things you control, not accidents.
1. "My container exits immediately"
You docker run -d something and it's gone a second later. A container lives
exactly as long as its main process (the CMD) — if that process finishes or
crashes, the container stops. Common causes: the app crashed on startup (check
docker logs <name>), or the command was something that completes instantly
(a script that runs and exits) rather than a long-running server. The diagnosis is always the
same two commands: docker ps -a to see it exited, docker logs to see
why.
2. Data vanished with the container
If anything wrote data you cared about inside the container, deleting the container deleted the data. Databases, uploads, generated files — anything that must survive belongs on a volume (Lesson 4). The bitter version of this lesson is losing a development database; the catastrophic version is doing it in production. Volumes from day one.
3. The 2 GB image that should be 80 MB
Three classic causes, three fixes:
- Fat base image —
FROM nodeis ~1 GB;FROM node:22-alpineis ~130 MB. Prefer-alpineor-slimvariants. - No
.dockerignore— you're copyingnode_modules,.git, and build artifacts into the image. - Build tools shipped to production — compilers and dev dependencies needed only at build time. The fix is a multi-stage build: build in one stage, then
COPY --from=buildonly the output into a minimal final image.
4. ":latest" isn't what you think
:latest is just a tag name, not a promise — it points at whatever was most
recently pushed without an explicit tag, and your machine won't re-pull it once it has
a version cached. Two machines running myapp:latest can be running
different code. Pin real version tags in anything that matters.
5. Running everything as root
By default, the process inside most containers runs as root. Combined with a bind mount,
that's a container that can rewrite files on your host as root. Official images increasingly
provide a non-root user — use it with the USER instruction in your Dockerfile
(e.g. USER node in Node images). One line, meaningfully less blast radius.
🙋 There Are No Dumb Questions
Q: My disk is filling up and I barely use Docker. Where did the space go?
A: Old images, stopped containers, build cache, and orphaned volumes accumulate silently —
every rebuild leaves the previous image layers behind. See the damage with
docker system df, and reclaim it with docker system prune
(add --volumes only if you're sure no unused volume holds data you want).
Run it every few weeks.
⚠️ Watch Out
Never put secrets in a Dockerfile — not as ENV, not as a RUN echo,
not "temporarily." Every instruction's result is baked into image history, and
docker history shows it to anyone who has the image. Pass secrets at
runtime (environment variables, Compose env_file, or your platform's
secret store), never at build time.
🔧 Try It Yourself
Audit your own machine right now:
docker system df # how much space Docker is using
docker ps -a # any forgotten stopped containers?
docker images # any :latest tags or giant images?
docker system prune # clean up (read the prompt before confirming)
If you've done every lesson in this track, there's probably a stray Nginx or Postgres in that list — proof the habit is worth having.
✅ Quick Check
A container started with docker run -d exits after one second. What's the first diagnostic step?
That's the track. Head to the recap for the whole picture in one place — and where to go next.