Docker & Containerization
The technology behind "it works on my machine" no longer being an acceptable excuse.
7.6.1Definition
Containerization packages an application together with everything it needs to run — dependencies, system libraries, runtime — into a single, portable unit called a container. Docker is the dominant tool for building, running, and sharing containers, ensuring the exact same environment runs identically on a developer's laptop, a CI pipeline, and a production server.
7.6.2Why It Exists
Before containerization, "it works on my machine" was a genuine, common failure — subtle differences in installed library versions or OS configuration between environments caused bugs that only appeared in production. Docker exists to eliminate that entire category of problem by packaging the application's complete runtime environment once and running that identical package everywhere, rather than each environment maintaining its own separately-configured setup.
7.6.3Core Concepts
- Image — a built, immutable snapshot of an application and its environment, defined by a Dockerfile.
- Container — a running instance of an image, isolated from the host system and other containers.
- Dockerfile — the text file defining exactly how an image is built, step by step, making the environment reproducible from source.
- Registry — a storage/distribution system (Docker Hub, a private registry) for sharing built images between environments.
7.6.4Common Mistakes
- Bloated images including unnecessary build tools or files in the final image, increasing size and attack surface for no runtime benefit.
- Running containers as root by default, unnecessarily widening the security impact if the container is compromised.
- Baking secrets directly into an image, where they persist in image layers even if later "removed" in a subsequent instruction.
- No explicit version pinning for base images, causing a rebuild months later to silently pull a different, potentially incompatible base.
7.6.5Best Practices
- Use multi-stage builds to keep final images minimal, excluding build-only dependencies.
- Run containers as a non-root user unless there's a specific reason not to.
- Inject secrets at runtime via environment variables or a secrets manager, never baked into the image itself.