Containers vs Virtual Machines
Virtual Machine (VM): Emulates hardware. Runs a full Guest OS on top of a Hypervisor. Heavy (GBs). Container: Shares the Host OS Kernel. Lightweight (MBs). Fast startup (ms).
Containers are process isolation mechanisms provided by the Linux Kernel.
The Three Pillars of Containers
How does docker run actually work? It uses three Linux features:
1. Namespaces (Isolation)
Namespaces limit what a process can see.
| Namespace | What it Isolates |
|---|---|
| PID | Process Key Numbers. Container thinks it's PID 1. |
| NET | Network Interfaces (eth0), Ports, Routing Tables. |
| MNT | Mount Points. Container has its own root /. |
| UTS | Hostname and Domain. |
| IPC | Inter-Process Communication (Shared Memory). |
| USER | User IDs. Root in container != Root on host. |
Try it: unshare --fork --pid --mount-proc /bin/bash creates a new PID namespace.
2. Control Groups (Cgroups) (Resource Limiting)
Cgroups limit how much a process can use.
- Memory: "You get 512MB RAM." (
OOM Killif exceeded) - CPU: "You get 0.5 CPU cores."
- Block I/O: "Limit disk read speed to 10MB/s."
Accessed via file system: /sys/fs/cgroup/.
3. Union Filesystem (Storage)
How are images so efficient?
Docker Images are built from Layers.
- Layers: Read-only changesets (e.g., Ubuntu Base -> Add Python -> Add App Code).
- Union Mount: Merges all layers into a single view.
- Copy-on-Write (COW): If a container modifies a file from a lower layer, it copies it up to the writable top layer first.
OverlayFS is the modern implementation.
Docker Architecture
Client-Server model.
- Docker CLI: Sends commands (
docker run) to Daemon via REST API (Unix Socket). - Docker Daemon (
dockerd): Manages objects (images, containers, networks). - Containerd: High-level runtime. Manages image pull/push and lifecycle.
- Runc: Low-level runtime. Spawns the actual process using syscalls.
Building Efficient Images
1. Multi-Stage Builds
Build in a fat image (Go compiler), copy binary to tiny image (Alpine/Scratch).
# Stage 1: Build FROM golang:1.21 AS builder WORKDIR /app COPY . . RUN go build -o myapp main.go # Stage 2: Run FROM alpine:latest WORKDIR /root/ COPY --from=builder /app/myapp . CMD ["./myapp"] # Result: 10MB image (instead of 800MB)
2. Layer Caching
Order matters! Put frequently changing instructions (COPY code) at the bottom.
# BAD: Breaks cache if source code changes COPY . . RUN npm install # GOOD: Caches dependencies unless package.json changes COPY package.json . RUN npm install COPY . .
Security: Container Breakout
If a container runs as root, and a vulnerability exists in the Kernel...
Container Breakout: Attacker escapes the container and gains access to the Host.
Defenses:
- Run as Non-Root:
USER appuserin Dockerfile. - Read-Only Root:
docker run --read-only. - Capabilities: Drop unused Linux privileges (
--cap-drop ALL --cap-add NET_BIND_SERVICE).
Containers vs. VMs: Where the Line Actually Is
"Containers are lightweight VMs" is the most common — and most wrong — mental model. The difference is architectural:
| Virtual Machine | Container | |
|---|---|---|
| Kernel | Own guest kernel per VM | Shares the host kernel |
| Isolation boundary | Hardware virtualization (hypervisor) | Kernel namespaces + cgroups |
| Startup | Seconds–minutes (boot an OS) | Milliseconds (start a process) |
| Density | Tens per host | Hundreds–thousands per host |
| Security boundary | Strong (separate kernels) | Weaker (one kernel bug from escape) |
A container is just a normal Linux process wearing three disguises: namespaces (it sees its own filesystem, network, and PIDs), cgroups (it's limited in CPU/memory/IO), and a union filesystem (its disk is stacked image layers plus a writable top). Run ps aux on a Docker host and every containerized process is right there in the list.
This is also why the security note above matters so much: all containers on a host share one kernel. For hostile multi-tenancy (running customers' arbitrary code), the industry adds a stronger boundary back — gVisor (userspace kernel proxy) or Firecracker microVMs (AWS Lambda's approach: real VM isolation with ~125ms boot times) — landing deliberately between the two columns of the table.
What Actually Happens on docker run
Demystifying the flow end-to-end:
- CLI → daemon:
docker run nginxis an API call todockerd. - Image resolution: missing layers are pulled from the registry, verified by digest, and stored content-addressed (a layer shared by 50 images exists on disk once).
- Filesystem assembly: overlayfs mounts the image's read-only layers with a fresh writable layer on top.
- Delegation: dockerd hands the spec to containerd, which invokes runc — the small OCI runtime that actually creates the namespaces and cgroups, then
execs your entrypoint as PID 1 inside them. runc exits; a tiny shim stays as the process's parent.
The layering explains real operational facts: Kubernetes talks to containerd directly (Docker the daemon is unnecessary in production clusters), a dockerd restart doesn't kill running containers (the shims own them), and "distroless" images work because a container needs no OS — just your binary and its libraries, since the kernel comes from the host.
One production gotcha worth internalizing: your entrypoint runs as PID 1, and PID 1 has special signal semantics — default handlers don't apply. Naive entrypoints ignore SIGTERM, so docker stop waits 10 seconds then SIGKILLs them — that's where mysterious slow shutdowns and dropped in-flight requests come from. Use a tiny init (--init/tini) or handle signals explicitly.
Related Concepts
- Kubernetes Architecture (Orchestrates containers)
- CI/CD Pipelines (Builds images)
- Microservices (Deployed in containers)
- Process vs Thread (The primitives underneath)
- Serverless Architecture (Firecracker microVMs in action)
About ScaleWiki
ScaleWiki is an interactive educational platform dedicated to demystifying distributed systems, software architecture, and system design. Our mission is to provide high-quality, technically accurate resources for software engineers preparing for interviews or solving complex scaling challenges in production.
Read more about our Editorial Guidelines & Authorship.
Educational Disclaimer: The architectural patterns and system designs discussed in this article are based on common industry practices, technical whitepapers, and public engineering blogs. Actual implementations in enterprise environments may vary significantly based on specific product requirements, legacy constraints, and evolving technologies.
Related Articles
Kubernetes Architecture Explained
Under the hood of K8s: The Control Plane (API Server, Scheduler, Etcd, Controllers) and Data Plane (Kubelet, Kube-proxy, Container Runtime).
Blue-Green Deployment
Zero-downtime deployment strategy using two identical production environments (Blue and Green) to enable instant rollbacks, reduce risk, and allow thorough testing before directing traffic.
CI/CD Pipeline Architecture
Designing robust Continuous Integration and Continuous Deployment pipelines. Strategies for artifact promotion, testing pyramids, canary deployments, and rollback mechanisms.