Every developer has heard some version of this story: "It works on my machine." The code runs perfectly locally. It's been tested. QA signed off. Then it hits production and... nothing works. Different OS, different Python version, missing libraries, conflicting dependencies.
Docker's solution to this problem is elegantly simple: ship the machine too.
What Is Docker, Really?
Docker packages your application and everything it needs — runtime, libraries, config, environment variables — into a single standardized unit called a container. Containers are:
- Isolated — They don't interfere with each other or with the host system
- Portable — A container that runs on your laptop runs identically in production
- Lightweight — Unlike VMs, containers share the host OS kernel. Startup is measured in milliseconds, not minutes
- Reproducible — The Dockerfile is a recipe that builds the same image every time
A container is an instance of an image. An image is built from a Dockerfile. Think of it: Dockerfile → Image → Container :: Source code → Executable → Running process.
Your First Dockerfile
Let's containerize a simple Python web app:
# Start from an official base image
FROM python:3.12-slim
# Set working directory inside the container
WORKDIR /app
# Copy dependency file first (for layer caching)
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Copy the rest of the application
COPY . .
# Tell Docker which port the app listens on
EXPOSE 8000
# The command to run when the container starts
CMD ["python", "-m", "uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
requirements.txt and install dependencies before copying your app code. That way, if only your code changes, Docker skips the slow dependency installation step.
Build and run:
# Build an image tagged "my-app"
docker build -t my-app .
# Run a container from that image
docker run -p 8000:8000 my-app
# Run in background (detached)
docker run -d -p 8000:8000 --name my-app-container my-app
# See running containers
docker ps
# Stop it
docker stop my-app-container
Docker Compose: Multiple Containers
Real apps rarely run in a single container. You've got a web server, a database, a cache, a message queue. Docker Compose lets you define and run multi-container apps:
version: '3.8'
services:
web:
build: .
ports:
- "8000:8000"
environment:
- DATABASE_URL=postgresql://user:pass@db:5432/mydb
- REDIS_URL=redis://cache:6379
depends_on:
- db
- cache
volumes:
- ./src:/app/src # mount local code for development
db:
image: postgres:16
environment:
POSTGRES_USER: user
POSTGRES_PASSWORD: pass
POSTGRES_DB: mydb
volumes:
- postgres_data:/var/lib/postgresql/data
cache:
image: redis:7-alpine
command: redis-server --maxmemory 256mb --maxmemory-policy allkeys-lru
volumes:
postgres_data:
docker compose up -d # Start everything in background
docker compose logs -f # Follow logs
docker compose down # Stop and remove containers
Enter Kubernetes: Containers at Scale
Docker Compose is great for development and small deployments. But what happens when you need to:
- Run 50 instances of your web service across multiple servers?
- Automatically restart containers that crash?
- Roll out updates with zero downtime?
- Scale up when traffic spikes and scale down to save money?
That's Kubernetes (K8s). It's a container orchestrator — a system for deploying, managing, and scaling containerized workloads across clusters of machines.
Core K8s Concepts
Kubernetes has a steep learning curve, but it builds on a few core abstractions:
- Pod — The smallest deployable unit. Usually one container, sometimes a few tightly coupled ones.
- Deployment — Declares the desired state: "I want 3 replicas of this pod running at all times."
- Service — A stable network endpoint for a set of pods. Pods come and go; Services persist.
- Ingress — Routes external traffic into Services based on hostname/path rules.
- ConfigMap / Secret — Environment-specific configuration, decoupled from the container image.
- Namespace — Virtual cluster for isolating resources (dev/staging/prod in one physical cluster).
Your First K8s Deployment
First, get a local Kubernetes cluster. kind (Kubernetes in Docker) is the easiest:
# Install kind (on macOS/Linux)
brew install kind
# or
curl -Lo ./kind https://kind.sigs.k8s.io/dl/v0.22.0/kind-linux-amd64 && chmod +x ./kind
# Create a cluster
kind create cluster --name my-cluster
# Verify
kubectl get nodes
Now write a deployment YAML:
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
namespace: default
spec:
replicas: 3
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
app: my-app
spec:
containers:
- name: my-app
image: my-app:latest
ports:
- containerPort: 8000
resources:
requests:
memory: "64Mi"
cpu: "100m"
limits:
memory: "128Mi"
cpu: "500m"
readinessProbe:
httpGet:
path: /health
port: 8000
initialDelaySeconds: 5
periodSeconds: 10
---
apiVersion: v1
kind: Service
metadata:
name: my-app-service
spec:
selector:
app: my-app
ports:
- port: 80
targetPort: 8000
type: ClusterIP
kubectl apply -f deployment.yaml
kubectl get pods # See running pods
kubectl get deployments # Deployment status
kubectl rollout status deployment/my-app # Wait for rollout
kubectl logs -f deployment/my-app # Stream logs
Zero-Downtime Updates
One of Kubernetes' killer features is rolling updates:
# Update the image
kubectl set image deployment/my-app my-app=my-app:v2
# K8s will gradually replace pods with the new version
# keeping at least N replicas healthy throughout
# Something broke? Roll back instantly
kubectl rollout undo deployment/my-app
Where to Host Your Cluster
For production, managed Kubernetes services eliminate the operational burden of running control planes:
- Google Kubernetes Engine (GKE) — The most mature, K8s originated at Google
- Amazon EKS — Best if you're already in the AWS ecosystem
- Azure AKS — Great Azure/enterprise integration
- DigitalOcean DOKS — Simpler, cheaper, great for smaller projects
- Cloudflare + Workers — For edge-native deployments without managing nodes at all
Next Steps
You now understand containers (Docker) and orchestration (Kubernetes) at a conceptual and practical level. The path forward:
- Practice with
kindlocally — deploy a real multi-tier app - Learn Helm (the Kubernetes package manager)
- Explore namespaces, RBAC, and NetworkPolicy for production hardening
- Set up a GitOps workflow with ArgoCD or Flux
- Get certified: CKA (Certified Kubernetes Administrator) is the gold standard
The container ecosystem is vast but extremely well-documented. You're now equipped to navigate it.