TL;DR
- A container packages your app and its dependencies to run as an isolated process on a host sharing the OS kernel.
- A virtual machine (VM) packages a full OS with virtualized hardware—heavier but with stronger isolation and its own kernel.
- Containers: fast start (ms–s), small images, high density, great for microservices/CI/CD.
- VMs: strong isolation, any kernel/OS, good for untrusted/privileged or system-level workloads.
- Value: portability, repeatable deploys, resource efficiency, and dev→prod parity.
The mental model (in one minute)
Process isolation + Filesystem snapshot + Network namespace + Resource limits
│ │ │ │
(PID/ns) (image layers) (bridge) (cgroups)
- A container is just a process with its own view of the world (PID, mount, network namespaces) and resource limits (cgroups).
- An image is a stack of read‑only layers; a container adds a thin, writable layer on top.
- The host kernel is shared; there’s no guest kernel like in a VM.
VM vs Container (quick compare)
| Aspect | Container | Virtual Machine | |---|---|---| | Isolation unit | Process (namespaces/cgroups) | Full OS (hypervisor) | | Kernel | Shared with host | Own guest kernel | | Boot/start time | ms–seconds | seconds–minutes | | Image size | 10s–100s MB | GBs | | Density | High (many per host) | Lower | | Portability | Great (OCI images) | Good (images/AMIs), heavier | | Security | Good, but shared kernel; harden config | Stronger isolation boundary | | Best for | Microservices, CI, ephemeral tasks, autoscaling | Strong isolation, legacy OS, kernel-specific workloads |
Core building blocks
- Image: immutable snapshot (layers). Built from a Dockerfile (or Buildpacks).
- Container runtime:
containerd/runc(Docker uses these under the hood) — OCI standard compliant. - Registry: where images live (Docker Hub, GHCR, ECR, GCR).
- Orchestration: Docker Compose for local/dev; Kubernetes/ECS/Nomad for fleets.
Minimal, real-world examples
A tiny Dockerfile (Node.js)
# 1) smaller base for prod
FROM node:20-alpine AS base
# 2) install deps separately for better caching
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
# 3) copy app code
COPY . .
# 4) run as non-root
USER node
EXPOSE 3000
CMD ["node", "server.js"]
Build & run
docker build -t myapp:latest .
docker run -p 3000:3000 --rm myapp:latest
Compose: app + Postgres
version: "3.9"
services:
api:
build: .
ports: ["3000:3000"]
environment:
DATABASE_URL: postgres://postgres:postgres@db:5432/app
depends_on: [db]
db:
image: postgres:16
environment:
POSTGRES_PASSWORD: postgres
volumes:
- pgdata:/var/lib/postgresql/data
volumes:
pgdata:
Why containers feel faster & cheaper
- No guest OS → fewer resources per instance.
- Layer caching → rebuilds push only changed layers.
- Immutable images → “works on my machine” becomes works anywhere.
- Horizontal scale → start/stop containers quickly to match load.
- Dev/Prod parity → same image in CI, staging, and prod.
Persistence & networking (gotchas)
- Containers are ephemeral: store state on volumes or external services (managed DBs, object storage).
- Networking defaults to a bridge; expose ports with
-p 8080:80. Compose/K8s add service discovery. - Logs go to stdout/stderr; ship them via drivers or sidecars.
Security basics (must-do)
- Don’t run as root: set
USERin Dockerfile. - Use small/base images (Alpine, distroless) and scan for CVEs.
- Keep secrets out of images (use env vars/secret managers).
- Apply resource limits (
--cpus,--memory) and read‑only filesystems when possible. - Patch regularly; rebuild from updated bases.
When to pick VMs instead
- You need a different kernel/OS than the host (e.g., Windows kernel apps on Linux hosts).
- Hard multi‑tenant isolation or regulatory separation.
- System services that expect full OS control (kernels, device drivers).
Quick command cheat sheet
docker ps # list running containers
docker images # list images
docker logs -f <container> # tail logs
docker exec -it <container> sh # shell into a container
docker compose up -d # start services
docker system prune -f # cleanup dangling images/containers
One‑minute decision guide
- Stateless web/API, microservices, CI jobs? → Containers.
- Per-tenant isolation, custom kernel/OS, heavy system access? → VMs.
- Mixed? → Run apps in containers on VMs; use the best of both.