What Docker changes about running software
"It works on my machine" used to be the most expensive sentence in software.
Updated:
“It works on my machine” used to be the most expensive sentence in software. A program depended on a particular version of a language runtime, a handful of system libraries and a few environment variables, and reproducing that exact combination on another computer took hours. Docker solved the problem by packaging an application together with everything it needs into an image, then running that image in an isolated container on any machine with a Docker engine. This article explains what containers actually are, how they differ from virtual machines, and where they help and where they do not.
Images, containers and layers
A container is an ordinary process on the host operating system, but one the kernel has fenced off. Two Linux kernel features do the fencing. Namespaces give the process its own view of the world: its own process list, network interfaces, hostname, mount points and user IDs. Control groups (cgroups) limit how much CPU, memory and I/O it may consume. Add a private root file system, and the process believes it is alone on a small machine, while in reality it shares the host kernel with everything else.
An image is the read-only template a container starts from: a file system snapshot containing the application, its runtime and libraries, plus metadata such as the command to run and the ports it listens on. Images are built in layers, each the result of one instruction in a Dockerfile:
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY . .
EXPOSE 3000
CMD ["node", "server.js"]
Layers are cached and shared. Ten images based on the same Node layer store it once. Rebuilding after a source change reuses every layer above the change, so builds are fast. Images are pushed to a registry (Docker Hub, GitHub Container Registry, a private one) and pulled wherever they are needed, giving every environment a byte-for-byte identical application.
Containers versus virtual machines
A virtual machine emulates a whole computer and boots a complete guest operating system with its own kernel. That gives strong isolation but costs gigabytes of disk, hundreds of megabytes of memory and tens of seconds to start. A container shares the host kernel, so it starts in milliseconds, uses megabytes, and packs densely, dozens or hundreds per host. The trade-off is isolation: a kernel vulnerability can in principle let a container escape, so hostile workloads are still run in VMs, often with containers inside them. Docker Desktop on Windows and macOS runs a small Linux VM behind the scenes because Linux containers need a Linux kernel.
Everyday use
- Development environments.
docker compose upstarts an application together with its database, cache and message queue in one command, identical for every developer. - Trying software. Run a database, a monitoring tool or an old version of a language for an hour and delete it without leaving traces on the host.
- Continuous integration. Build and test inside the same image that will be deployed.
- Deployment. An image built once passes through staging to production unchanged. Orchestrators such as Kubernetes and Docker Swarm schedule containers across many hosts, restart failed ones and scale them.
- Self-hosting. Home servers commonly run a dozen services, each in its own container with its own configuration and updates.
Common pitfalls
- Data inside the container. A container’s writable layer disappears when the container is removed. Persistent data belongs in volumes or bind mounts.
- Running as root. Many images run their process as root by default. Set a non-root
USERin the Dockerfile. - Unpinned tags.
FROM node:latestbuilds something different every month. Pin versions. - Bloated images. Copying the whole repository, including build tools and tests, produces gigabyte images. Use slim base images,
.dockerignore, and multi-stage builds that copy only the artefacts. - Secrets in images. Anything baked into a layer is retrievable by anyone with the image. Pass secrets at runtime.
- Treating containers as pets. Logging in and editing files inside a running container defeats reproducibility. Change the Dockerfile and rebuild.
Starting with Docker on your own machine
- Use containers wherever you want a reproducible environment: development, testing, deployment, self-hosting.
- Keep state in volumes, configuration in environment variables, secrets outside the image.
- Pin base image versions and rebuild regularly to pick up security fixes.
- Scan images for known vulnerabilities as part of the build.
- Remember that a container is not a security boundary against a hostile kernel exploit; use VMs for untrusted code.
Frequently asked questions
Is Docker the only way to run containers?
No. Podman, containerd and CRI-O run the same OCI-standard images. Docker popularised the format and remains the most common developer tool.
Do containers make applications faster?
Not in themselves; a containerised process runs at native speed, neither faster nor slower. The gains are in consistency, density and deployment speed.
Do I need Kubernetes?
Only when you run many containers across many machines and need automated scheduling and recovery. A single host with Docker Compose serves a great many real applications.
A container is a process with a private view of the machine; an image is the frozen environment it wakes up in.
In short: Docker packages an application and its dependencies into layered images and runs them as isolated processes using kernel namespaces and cgroups. Containers are lighter than virtual machines and give identical environments everywhere, provided data lives in volumes, secrets stay out of images and versions are pinned.