The "matrix from hell"
A modern application has many stacks: web frontend, background workers, database, queue… Each stack needs its own libraries and runtime versions. The application must also run on a dev laptop, a QA server, a production cluster and a public cloud. Every cell of the "stack × environment" matrix is a question: will it work here?
Shipping before 1960 had the same problem: many kinds of goods × many kinds of transport. The answer was the intermodal shipping container — one standard box that ships, trucks and cranes can all handle, whatever is inside.
Docker is a "shipping container for code": you package the app with its dependencies into an image, then run that image as a container on any host that has Docker.
flowchart LR Code["App + libraries + runtime"] --> Img["Image app:1.0"] Img --> Dev["Dev laptop"] Img --> QA["QA server"] Img --> Prod["Production"]
What Docker solves — and what it does not
- Yes: consistency and portability. The same image goes from dev to prod, so "it works on my machine" disappears.
- No: Docker does not make code run faster. Per-environment settings (environment variables, secrets) are still passed in at run time.
- Linux containers need a Linux kernel, and the image must support the host CPU architecture (amd64, arm64).
| Command | Meaning |
|---|---|
docker run --rm -it busybox:1.36 sh |
Open a throwaway shell in a container, removed on exit |
docker version |
Check the Docker client and server |
Where containers fit in DevOps
The main gain is build once, run everywhere: the CI pipeline builds one image, tests it, and the very same image is promoted to staging and production. Only configuration differs between environments. This supports several practices:
- Immutable deployments: instead of patching servers, you replace containers with a new image version, and rolling back means running the previous tag.
- Microservices: each service has its own image, runtime, and release cycle.
- Developer environments:
docker compose upstarts the app with its database and cache on any laptop in minutes. - Density: many small containers share one host more efficiently than one VM per app.
A short history
Linux had the building blocks (namespaces, cgroups, LXC) before Docker, but Docker, released in 2013, made them easy with images, a registry, and a simple CLI. In 2015 the OCI was formed to standardise images and runtimes. Kubernetes later removed its Docker-specific adapter (dockershim) in version 1.24, yet images built with Docker still run on Kubernetes because they follow the OCI format.
Limits to keep in mind
| Challenge | Why |
|---|---|
| Persistent data | A container's own filesystem is temporary; databases need volumes or managed services |
| Security | Shared kernel, images with outdated packages, secrets baked into layers |
| Operations at scale | Scheduling, healing, and networking across many hosts need an orchestrator such as Kubernetes |
| Desktop and GUI apps | Containers target server processes, not graphical desktops |
Good first habits
Pin image versions instead of latest, keep one main process per container, write logs to stdout and stderr, and pass configuration through environment variables. These habits make the jump to Compose and Kubernetes much smoother later in this track.