Docker
Docker packages an application together with its dependencies and runtime into a container — an isolated process that behaves identically on a laptop, a CI runner, and a production server. It's the technology that turned "works on my machine" from an excuse into a non-issue.
Launched in 2013, Docker made Linux container technology accessible to mainstream developers with a simple CLI, a standard image format, and a registry (Docker Hub). Containers share the host's Linux kernel (unlike VMs, which each run their own), so they start in seconds and use minimal resources, and Docker now underpins modern CI/CD, orchestration, and cloud-native development.
TL;DR
- An image is an immutable template; a container is a running instance of it.
- A Dockerfile builds images in cached layers — order matters for build speed.
- Containers are ephemeral; use volumes for data that must persist.
- Run as non-root, pin image tags (not
latest), and use multi-stage builds.
Quick Example
A multi-stage Dockerfile keeps build tooling out of the final image, shrinking it and reducing attack surface:
Core Concepts
- Image — a read-only template of code, dependencies, and config, built in layers.
- Container — a running, isolated instance of an image.
- Dockerfile — the build recipe; each instruction creates a cached layer.
- Registry — stores and distributes images (Docker Hub, ECR, GHCR).
- Volume — persistent storage that survives container restarts.
- Network — virtual networks for container-to-container communication.
- Compose — defines multi-container apps (app + db + cache) in one YAML file.
💡 Containers aren't VMs — they share the host kernel via cgroups (resource limits) and namespaces (isolation), which is why they're so lightweight. Images are immutable (you build new ones, not modify), and data is ephemeral without volumes.
Architecture
Variants
Ecosystem
Docker Engine is open-source (Apache 2.0); Docker Desktop requires a paid subscription for larger enterprises (open alternatives: Podman, Rancher Desktop).
Performance & Security
Performance: image size (large images slow builds/deploys), layer ordering (put frequently-changing files last for cache hits), and bind-mount I/O on macOS/Windows are the usual pain points. Use multi-stage builds, .dockerignore, and minimal base images.
Security: run as non-root, scan images for CVEs, pin tags, drop capabilities and use a read-only filesystem, never mount the Docker socket unless required, and never bake secrets into images (they're visible in docker history). See Container Security.
Comparison
VMs win for strong isolation and running different OSes; Kubernetes wins for production microservices, auto-scaling, and self-healing. See Kubernetes and Podman.
Best Practices
- Multi-stage builds — keep dev dependencies out of the production image.
- Run as non-root (
USER) and scan images (Trivy, Scout). - Pin tags (
node:20.11), neverlatest, for reproducible builds. - Use a
.dockerignoreand order layers so frequently-changing files come last. - Set resource limits; never bake secrets into images.
Common Mistakes
Using the latest tag in production
Baking secrets into the image
Treating a container like a VM
FAQ
What's the difference between a container and a VM?
A VM virtualizes hardware and runs a full guest OS with its own kernel; a container shares the host kernel and isolates only the process. Containers are far lighter and faster to start, but offer weaker isolation than VMs — a different security model.
Docker or Kubernetes — which do I need?
They solve different problems. Docker builds and runs containers; Kubernetes orchestrates many containers across many machines (scaling, self-healing, rolling updates). Use Docker (and Compose) for development and simple deployments; add Kubernetes when you need production orchestration at scale.
Why use multi-stage builds?
To keep build-time tooling (compilers, dev dependencies) out of the final image. You build in one stage and copy only the artifacts into a slim runtime stage, producing smaller, faster, more secure images.
How do I handle secrets in Docker?
Never put them in the image or Dockerfile — they persist in layers and docker history. Inject them at runtime via environment variables, a secrets manager, or BuildKit's --secret for build-time needs. See Secrets Management.
How do I make my images smaller?
Use a minimal base (slim, Alpine, or distroless), multi-stage builds, a .dockerignore, and combine/order layers well. Tools like dive show what's bloating each layer.
Related Topics
- Kubernetes — Orchestrating containers at scale
- Podman — Daemonless, rootless alternative
- Container Security — Hardening images and runtimes
- CI/CD Pipelines — Building images in pipelines
- Caching Layers — Where Docker layer caching fits
- DevOps — The broader practice