One-liner: Containers package your application and all its dependencies into one portable unit that runs identically on any machine β eliminating "works on my machine" problems.
π The Problem Before Containers
Dev machine: Node 18, npm 9, Ubuntu 22
Staging server: Node 16, npm 8, CentOS 7
Production: Node 14, npm 6, Amazon Linux 2
Result: App works in dev, breaks in prod π₯
π‘ Containers vs Virtual Machines
Virtual Machine (VM)
[App A] [App B] [App C]
[Guest OS] [Guest OS] [Guest OS] β each VM has full OS (GBs)
[Hypervisor (VMware, KVM)]
[Host OS]
[Hardware]
Heavy: ~1 GB+ per VM, slow to start (minutes)
Container
[App A] [App B] [App C]
[Container Runtime (Docker)] β share host OS kernel
[Host OS]
[Hardware]
Lightweight: ~10-100 MB per container, starts in milliseconds
π³ Docker Core Concepts
Image
A read-only template β like a blueprint/snapshot of your app + dependencies.
# Dockerfile β recipe to build an image
FROM node:18-alpine # base image
WORKDIR /app # working directory
COPY package*.json ./ # copy dependency files
RUN npm ci --production # install dependencies
COPY . . # copy source code
EXPOSE 3000 # document port
CMD ["node", "server.js"] # start command
Build: docker build -t my-app:1.0 .
Container
A running instance of an image.
docker run -d \
-p 3000:3000 \
-e DATABASE_URL=postgres://... \
--name my-app \
my-app:1.0
Registry
Where images are stored and shared.
docker push myrepo/my-app:1.0 # push to Docker Hub / ECR
docker pull myrepo/my-app:1.0 # pull on another machine
Popular registries: Docker Hub, AWS ECR, GitHub Container Registry, GCR
π Dockerfile Best Practices
Layer Caching β Order Matters!
# β Bad β any code change invalidates npm install layer
FROM node:18-alpine
COPY . .
RUN npm ci
# β
Good β npm install layer cached unless package.json changes
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./ β copy just package files first
RUN npm ci β this layer cached if package.json unchanged
COPY . . β code changes only bust this layer
CMD ["node", "server.js"]
Multi-Stage Build (Small Final Image)
# Stage 1: Build
FROM node:18 AS builder
WORKDIR /app
COPY . .
RUN npm ci && npm run build
# Stage 2: Production (only compiled output)
FROM node:18-alpine AS production
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
CMD ["node", "dist/server.js"]
Result: Final image is tiny (no dev tools, no source, no test files).
.dockerignore
node_modules
.git
*.test.js
.env
dist
π§ Docker Compose β Multi-Container Local Dev
# docker-compose.yml
version: "3.8"
services:
app:
build: .
ports:
- "3000:3000"
environment:
- DATABASE_URL=postgres://postgres:pass@db:5432/mydb
- REDIS_URL=redis://redis:6379
depends_on:
- db
- redis
db:
image: postgres:15
environment:
POSTGRES_PASSWORD: pass
POSTGRES_DB: mydb
volumes:
- postgres_data:/var/lib/postgresql/data
redis:
image: redis:7-alpine
volumes:
postgres_data:
Start everything: docker compose up -d
Stop: docker compose down
βΈοΈ Container Orchestration: Kubernetes (K8s)
Docker runs containers on ONE machine. In production you need many machines.
Kubernetes orchestrates containers across a cluster of machines.
Key K8s Concepts
| Concept | What It Is |
|---|---|
| Pod | Smallest unit β one or more containers |
| Deployment | Manages pod replicas, rolling updates |
| Service | Stable network endpoint for a set of pods |
| Ingress | HTTP routing into the cluster (like an API Gateway) |
| ConfigMap | Non-sensitive config (env vars) |
| Secret | Sensitive config (passwords, API keys) |
| Namespace | Logical isolation within a cluster |
# Deployment β run 3 replicas of my-app
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
replicas: 3
selector:
matchLabels:
app: my-app
template:
spec:
containers:
- name: my-app
image: myrepo/my-app:1.0
ports:
- containerPort: 3000
resources:
requests:
memory: "128Mi"
cpu: "250m"
limits:
memory: "256Mi"
cpu: "500m"
K8s Benefits
- Auto-scaling β scale pods based on CPU/memory
- Self-healing β restart failed containers automatically
- Rolling updates β deploy new version with zero downtime
- Load balancing β distribute traffic across pods
- Secret management β inject secrets as env vars
Managed K8s Services
| Cloud | Service |
|---|---|
| AWS | EKS (Elastic Kubernetes Service) |
| GKE (Google Kubernetes Engine) | |
| Azure | AKS (Azure Kubernetes Service) |
π Container in CI/CD Pipeline
Developer pushes code β
[GitHub Actions / Jenkins]
1. Run tests
2. Build Docker image: docker build -t app:$GIT_SHA .
3. Push to ECR: docker push app:$GIT_SHA
4. Deploy to K8s: kubectl set image deployment/app app=app:$GIT_SHA
5. K8s rolling update β zero-downtime deploy
π¨ Diagram
The diagram shows:
- VM vs Container architecture layers
- Docker image β container lifecycle
- Docker Compose multi-service local setup
- K8s cluster with nodes, pods, services
π Key Takeaways
- Containers solve the environment consistency problem β build once, run anywhere
- Always use multi-stage builds to keep images small
- Docker Compose for local dev; Kubernetes for production orchestration
- Container images should be immutable β no SSH into containers to fix things; rebuild and redeploy
Top comments (0)