Immutable Infrastructure

Containerization abstracts the environment from the code, ensuring "it works on my machine" works everywhere, every time.

Process Isolation & Layer Caching
Dockerfile Builder
1
FROM node:20-alpine AS base
2
WORKDIR /app
3
COPY package*.json ./
4
RUN npm install --production
5
COPY . .
6
EXPOSE 3000
7
CMD ["node", "src/index.js"]
Instruction Insight

FROM node:20-alpine

The foundation. Multi-stage builds often start with a lightweight "Alpine" linux base to minimize security surface area and image size.

Resulting Layer: Root filesystem with Node.js runtime (~50MB)
Build Layer Stack
Layer 1 ~50 MB
node:20-alpine
Base Image
Layer 2 ~120 MB
npm install
Dependencies
Layer 3 ~2.4 MB
COPY . .
App Source
Virtualization Models
Virtual Machine
Hardware
Hypervisor
Guest OS
Bins/Libs
App
Container
Hardware
Host OS / Kernel
Container Runtime
Bins/Libs
App
Containers share the Host OS Kernel, making them lightweight and fast to start compared to the bulky Guest OS overhead of VMs.
Multi-Service Orchestration (docker-compose.yml)
services: web: build: . ports: ["3000:3000"] depends_on: [db, cache] environment: - DB_URL=postgres://db:5432
db: image: postgres:15-alpine volumes: - pgdata:/var/lib/postgresql/data
cache: image: redis:alpine restart: always

Service: Web

The main application service. depends_on ensures the database and cache containers start before the app, while ports maps the internal traffic to your host.

Why Containers Matter

Layer Caching

By ordering commands from "least likely to change" to "most likely," Docker reuses cached layers. A code change only rebuilds the final 2MB layer, not the 120MB npm install.

Multi-stage Builds

Use one large image to compile assets, then COPY --from=build into a tiny production image. This keeps node_modules and compilers out of production.

Shared Kernel

Containers use namespaces (isolation) and cgroups (resource limits) within the host kernel. This allows hundreds of containers on one machine.