Quick reference
1. Don't
COPY . . early in the file
Any code change busts the cache for every later step.
Do instead
COPY deps first, code lastDependencies cache; only code rebuilds on a change.
2. ❌ no .dockerignore — You copy .git, node_modules and secrets into the image. ✅ a .dockerignore file — Smaller, cleaner, safer images.
3. Don't
RUN apt-get install ... (no clean)The package cache stays baked into the layer.
Do instead
install, then rm -rf /var/lib/apt/lists/*
Same step, smaller image.
4. ❌ one big image for build + run — Compilers and dev tools ship to production. ✅ multi-stage build — Build in one stage, copy only the result to a slim one.
5. ❌ FROM ubuntu (full) — Hundreds of MB and packages you never use. ✅ a slim or distroless base — Less to download, patch and attack.
Explained
- COPY . . early in the file Docker caches layers, and a layer is invalidated when its input changes. Copy all your code near the top and every tiny code change busts the cache for everything below it, so dependencies reinstall every build. Instead: copy the dependency manifest first, install, then copy the code last. Now only the code layer rebuilds on a change.
- No .dockerignore Without it, COPY . . drags .git, node_modules, local env files and build artifacts into the image, making it bigger and sometimes leaking secrets. Instead: add a .dockerignore, just like .gitignore.
RUN apt-get install without cleaningThe downloaded package lists stay baked into that layer forever. Instead: in the same RUN, finish withrm -rf /var/lib/apt/lists/*. It must be the same step, because a later cleanup does not shrink an earlier layer.
- One big image for building and running If you compile in the final image, the compiler, headers and dev tools all ship to production, bloating the image and widening the attack surface. Instead: a multi-stage build. Compile in a build stage, then copy only the finished artifact into a slim runtime stage.
- A full base image FROM ubuntu pulls hundreds of MB and packages you never use. Instead: a -slim base, or distroless for compiled apps, so there is less to download, patch and attack.
The thread: order layers for caching, and ship only what runs.
In one line
Order layers. Ship less. A good Dockerfile is small, cached and has no build tools in it.
Measure it
docker history <image> | docker image lsMore notes: Docker & containers
- P277The Docker command cheat sheet
- P196Alpine vs Debian base images
- P1949 docker commands for building and shipping images
- P1649 docker compose commands for daily work
- P163Anatomy of a container image reference
- P152Docker Compose vs Kubernetes
- P147Quiz: 3 container questions
- P133Anatomy of a docker run command
Open in the notes app: search, filters, copy This note on GitHub