04 - Docker Containers
What Is a Container?
A container is a running instance of an image. Think of it like this:
- Image = class definition
- Container = object instance
You can run multiple containers from the same image, each isolated.
Container Lifecycle
docker create docker start
Image ──────────────► Created ─────────────► Running
│ │
│ docker stop │
│ ◄───────────────────┘
│ │
│ docker start
│ ►───────────────────┐
│ │
▼ ▼
Stopped ◄──────────── Running
│ │
docker rm docker kill
│ │
▼ ▼
Deleted Stopped → Deleted
States:
- Created: Container exists but hasn't started
- Running: Main process is active
- Paused: Process frozen (SIGSTOP)
- Stopped: Process exited (exit code available)
- Deleted: Container removed from disk
Essential Container Commands
Creating and Running
bash# Run a container (create + start) docker run nginx # Run detached (background) docker run -d nginx # Run with a name docker run -d --name webserver nginx # Run interactive (terminal attached) docker run -it ubuntu bash # Run and auto-remove when stopped docker run --rm -it alpine sh # Run with port mapping docker run -d -p 8080:80 nginx # Host port 8080 → Container port 80 # Run with multiple port mappings docker run -d -p 8080:80 -p 8443:443 nginx # Run with environment variables docker run -d -e MYSQL_ROOT_PASSWORD=secret mysql:8 # Run with resource limits docker run -d --memory=512m --cpus=1.5 nginx # Run with restart policy docker run -d --restart=unless-stopped nginx
Inspecting Containers
bash# List running containers docker ps # List ALL containers (including stopped) docker ps -a # Show only container IDs docker ps -q # Detailed container info docker inspect mycontainer # Get specific fields docker inspect --format='{{.State.Status}}' mycontainer docker inspect --format='{{.NetworkSettings.IPAddress}}' mycontainer # See container logs docker logs mycontainer docker logs -f mycontainer # Follow (tail -f) docker logs --tail 100 mycontainer # Last 100 lines docker logs --since 1h mycontainer # Last hour # See resource usage (live) docker stats docker stats mycontainer # See running processes inside container docker top mycontainer # See port mappings docker port mycontainer # See filesystem changes vs the image docker diff mycontainer
Interacting with Running Containers
bash# Execute a command in a running container docker exec mycontainer ls /app # Get an interactive shell docker exec -it mycontainer bash docker exec -it mycontainer sh # Alpine doesn't have bash # Execute as a specific user docker exec -u root mycontainer whoami # Copy files between host and container docker cp mycontainer:/app/logs ./logs # Container → Host docker cp ./config.json mycontainer:/app/ # Host → Container # Attach to container's main process (stdin/stdout) docker attach mycontainer # Ctrl+P, Ctrl+Q to detach without stopping
Stopping and Removing
bash# Stop gracefully (SIGTERM, then SIGKILL after 10s) docker stop mycontainer # Stop with custom timeout docker stop -t 30 mycontainer # Kill immediately (SIGKILL) docker kill mycontainer # Remove a stopped container docker rm mycontainer # Force remove a running container docker rm -f mycontainer # Remove all stopped containers docker container prune # Stop and remove ALL containers docker stop $(docker ps -q) docker rm $(docker ps -aq)
Port Mapping In Depth
Host Machine
┌──────────────────────────────────────┐
│ │
│ Browser → localhost:8080 │
│ │ │
│ ▼ │
│ ┌─── Port Mapping (iptables) ───┐ │
│ │ 8080 → 172.17.0.2:80 │ │
│ └───────────────────────────────┘ │
│ │ │
│ ┌───────── Container ──────────┐ │
│ │ nginx listening on :80 │ │
│ │ IP: 172.17.0.2 │ │
│ └──────────────────────────────┘ │
└──────────────────────────────────────┘
bash# Syntax: -p [host_ip:]host_port:container_port[/protocol] # Map to all interfaces docker run -d -p 8080:80 nginx # Map to specific interface (localhost only) docker run -d -p 127.0.0.1:8080:80 nginx # Map UDP port docker run -d -p 5000:5000/udp myapp # Random host port (Docker picks one) docker run -d -p 80 nginx docker port <container> # See which port was assigned # Map all EXPOSE'd ports to random host ports docker run -d -P nginx
Environment Variables
bash# Single variable docker run -e DATABASE_URL=postgres://db:5432/mydb myapp # From a file # .env file: # DATABASE_URL=postgres://db:5432/mydb # REDIS_URL=redis://cache:6379 # SECRET_KEY=mysecret docker run --env-file .env myapp # Pass host environment variable export API_KEY=abc123 docker run -e API_KEY myapp # Passes host's API_KEY value
Restart Policies
| Policy | Behavior |
|---|---|
no | Never restart (default) |
on-failure[:max] | Restart only on non-zero exit code |
always | Always restart, even on manual stop (starts on daemon restart) |
unless-stopped | Like always but not after manual docker stop |
bash# Restart up to 5 times on failure docker run -d --restart=on-failure:5 myapp # Always restart (good for production services) docker run -d --restart=unless-stopped myapp # Update restart policy on existing container docker update --restart=unless-stopped mycontainer
Resource Limits
Memory
bash# Hard memory limit (OOM killed if exceeded) docker run -d --memory=512m myapp # Memory + swap limit docker run -d --memory=512m --memory-swap=1g myapp # Memory reservation (soft limit) docker run -d --memory=1g --memory-reservation=512m myapp
CPU
bash# Limit to 1.5 CPUs docker run -d --cpus=1.5 myapp # CPU shares (relative weight, default 1024) docker run -d --cpu-shares=512 myapp # Half priority docker run -d --cpu-shares=2048 myapp # Double priority # Pin to specific CPU cores docker run -d --cpuset-cpus="0,1" myapp # Use cores 0 and 1
PIDs
bash# Limit number of processes (prevent fork bombs) docker run -d --pids-limit=100 myapp
Health Checks
bash# In Dockerfile HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \ CMD curl -f http://localhost:8080/health || exit 1 # Override at runtime docker run -d \ --health-cmd="wget -q --spider http://localhost:8080/health || exit 1" \ --health-interval=30s \ --health-timeout=3s \ --health-retries=3 \ myapp # Check health status docker inspect --format='{{.State.Health.Status}}' mycontainer # healthy, unhealthy, or starting # See health check log docker inspect --format='{{json .State.Health}}' mycontainer | jq
Container Logging
bash# Default log driver: json-file docker logs mycontainer # Configure log driver per container docker run -d \ --log-driver=json-file \ --log-opt max-size=10m \ --log-opt max-file=3 \ myapp # Available log drivers # json-file - Default, stored on disk as JSON # syslog - Send to syslog daemon # journald - Send to systemd journal # fluentd - Send to Fluentd # awslogs - Send to CloudWatch # gcplogs - Send to Google Cloud Logging # none - Disable logging
Container as Processes
Containers are just Linux processes with extra isolation:
bash# On the host, you can see container processes ps aux | grep nginx # The container's PID 1 maps to a regular PID on the host docker inspect --format='{{.State.Pid}}' mycontainer # Returns: 12345 # You can inspect the process's namespaces ls -la /proc/12345/ns/
PID 1 and Signal Handling
The container's main process (PID 1) has special behavior:
- Receives all signals (SIGTERM, SIGINT, etc.)
- Must handle SIGTERM for graceful shutdown
- If PID 1 exits, the container stops
dockerfile# BAD: Shell form -- /bin/sh is PID 1, your app doesn't get signals CMD npm start # GOOD: Exec form -- your app is PID 1, receives signals directly CMD ["node", "server.js"] # Or use tini/dumb-init as PID 1 (handles zombie processes) RUN apk add --no-cache tini ENTRYPOINT ["tini", "--"] CMD ["node", "server.js"]
FAANG Interview Angle
Common questions:
- "What happens when a container's PID 1 process crashes?"
- "How do you debug a container that keeps restarting?"
- "Explain the difference between
docker stopanddocker kill" - "How would you limit a container's resources in production?"
- "What's the difference between
execandattach?"
Key answers:
- Container stops when PID 1 exits; restart policy determines what happens next
- Debug: check logs, exec into container, inspect events, check health
stopsends SIGTERM (graceful),killsends SIGKILL (immediate)- Use
--memory,--cpus, and health checks; set restart policies execstarts a new process;attachconnects to PID 1's stdio