Docker
In short: A platform for packaging applications into isolated, portable containers.
In more detail: A container bundles an application with all its dependencies (libraries, runtime) into one package that runs identically on any system with Docker — solves the “works on my machine” problem. Unlike a virtual machine, containers share the host operating system’s kernel, which makes them considerably more lightweight.
In Depth
Origin
Docker appeared in 2013 and struck a nerve: container technology already existed in the Linux kernel before that (LXC, cgroups, namespaces had partly existed since 2007), but there was no unified, easy-to-use tooling around it. Docker turned a niche technology for kernel experts into a tool any developer could use with a few commands, and, with the Dockerfile and image format, defined a de-facto standard that even competing runtimes like Podman or containerd support today.
Dockerfile, image, container
FROM node:20-alpine
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --production
COPY . .
EXPOSE 3000
CMD ["npm", "start"]A Dockerfile declaratively describes, layer by layer, how an image is built — each line creates its own, cacheable layer. This also explains the usual order in the example: package.json is copied BEFORE the rest of the code, so npm ci only reruns (and thereby slows down the build) when dependencies have actually changed — if only the application code changes, Docker can reuse the expensive install layer from the cache. docker build produces an image from the Dockerfile (an immutable, versioned snapshot of all layers), docker run starts one or more running containers from it.
Why containers are more lightweight than VMs
A virtual machine emulates complete hardware including its own operating-system kernel — every VM brings its own complete OS, which costs several gigabytes and noticeable boot overhead. A container, by contrast, shares the host system’s kernel and only isolates processes, file system and network at the operating-system level, via Linux kernel features: namespaces (every container only sees its own processes/network interfaces/file systems) and cgroups (limit how much CPU/RAM a container is allowed to use). This lets containers start in a fraction of a second instead of minutes, often need only megabytes instead of gigabytes, and lets dozens run on a single machine.
Several containers: docker-compose and orchestration
# docker-compose.yml
services:
app:
build: .
ports: ["3000:3000"]
depends_on: [db]
db:
image: postgres:16
environment:
POSTGRES_PASSWORD: secretIn practice, rarely does only a single container run: docker-compose starts several related containers (application, database, cache, reverse proxy) with a single configuration file and automatically sets up a shared network between them, so they can reach each other via their service names (db instead of an IP address). For running many containers in production across several physical machines (automatically restarting failed containers, load balancing, scaling), Kubernetes usually handles the orchestration — considerably more complex than Compose, but the industry standard for large, highly available systems.
Sharing images: registries
A built image can be uploaded to a registry (docker push), usually Docker Hub or a private provider — from there, any other machine can download it (docker pull) and start it identically. This is the core of the “works on my machine” problem Docker solves: instead of distributing code across different environments and hoping they’re configured identically, you distribute a finished, self-contained image that runs exactly the same everywhere.
See also: Package, Linux, CI/CD Pipeline