Containers & orchestrationIntermediate5h

Dockerfile best practices.

Layer caching, multi-stage builds, and small secure images.

What makes a good Dockerfile?

A good Dockerfile builds fast, produces a small image, and exposes little attack surface. It orders instructions so the cache is reused, uses multi-stage builds to drop build tooling from the final image, and runs as a non-root user.

Why it matters

Image size and build time directly affect how fast you ship and how much you pay. A bloated image is slower to push and pull and carries more vulnerabilities. These practices are the difference between a 1GB image that takes minutes to deploy and a 50MB one that ships in seconds.

What to learn

  • Ordering instructions so dependency layers cache
  • Multi-stage builds to separate build and runtime
  • Choosing a small, secure base image
  • .dockerignore to shrink the build context
  • Running as a non-root user
  • Pinning versions for reproducibility
  • Scanning images for vulnerabilities

Common pitfall

Copying the whole project before installing dependencies, so every code change busts the dependency cache and reinstalls everything. Copy the lockfile, install dependencies, then copy the rest — that way a code-only change reuses the cached dependency layer and the build stays fast.

Resources

Primary (free):

Practice

Take a working Dockerfile and improve it: reorder so dependencies cache before code, convert it to a multi-stage build, switch to a slim base, add a .dockerignore, and run as non-root. Compare the image size before and after. Done when the final image is dramatically smaller and rebuilds are fast.

Outcomes

  • Order instructions to maximize layer cache reuse.
  • Shrink images with multi-stage builds and slim bases.
  • Reduce the build context with .dockerignore.
  • Run containers as a non-root user.
Back to DevOps roadmap