Docker
Docker = Linux containers + smart packaging
Think of Docker as "shipping containers for software": - Same app runs identically on your laptop, AWS, Google Cloud, or your friend’s PC. - No more "It works on my machine!"
Your App (Python, Node.js, Java)
↓
Docker packages it with EXACT OS libraries
↓
Runs anywhere Docker exists
1. Docker vs Traditional Setup
Traditional: Docker:
+--------------------+ +--------------------+
| App | | App + Dependencies |
| Dependencies | | (one tiny layer) |
| OS (Ubuntu 20.04) | | Host OS (any Linux) |
| Hardware | | Docker Engine |
+--------------------+ +--------------------+
Docker removes the "guest OS" → 100x lighter than VMs.
Docker Architecture – What Runs Where?
+-----------------+
| Docker Desktop | ← GUI for Windows/Mac
| Docker CLI | ← You type commands
+-----------------+
↓
+-----------------+
| Docker Daemon | ← The brain (dockerd)
| (Docker Engine) |
+-----------------+
↓
Linux Kernel (cgroups, namespaces) ← Real magic happens here
Key Components
| Component | Job |
|---|---|
| Docker CLI | docker run, docker ps |
| Docker Daemon | Manages containers, images, networks |
| Docker Desktop | Runs Linux VM on Windows/Mac |
| containerd | Lightweight runtime (replaced Docker shim) |
| runc | Actually starts containers |
2. Docker Under the Hood: Linux Kernel Magic (cgroups + namespaces)
How Docker Creates "Lightweight VMs" Without a Hypervisor
Docker doesn't invent isolation — it uses Linux kernel features
→ Namespaces = "What the container sees"
→ cgroups = "What the container can use"
Think of it like putting your app in a private apartment: - Namespaces → give it its own view of the building (files, network, users) - cgroups → give it a limited budget (CPU, RAM, electricity)
Let’s break it down.
1. Namespaces – "Private Apartments for Processes"
Linux has 7 types of namespaces. Docker uses 6 of them.
| Namespace | What It Isolates | Docker Example |
|---|---|---|
| PID | Process IDs | Container sees pid 1 as its init, not host's |
| NET | Network interfaces, IP, ports | Container gets 172.17.0.2, can't see host Wi-Fi |
| MNT | Filesystem mount points | / inside container ≠ host / |
| UTS | Hostname & domain | Container can be named web-01 |
| IPC | Inter-process communication | shm only shared within container |
| User | User & group IDs | root in container ≠ real root (UID 1000) |
Real Example: PID Namespace
# On host
ps aux | grep nginx
# → PID 1234 (real system PID)
# Inside container
docker exec mynginx ps aux
# → PID 1 → nginx (thinks it's the only process!)
No other container or host can see its PIDs.
2. cgroups (Control Groups) – "Resource Budget Manager"
cgroups = Linux's way to limit, account, and isolate resource usage (CPU, memory, disk I/O, network).
Docker uses cgroups to prevent one container from starving others.
Key cgroups Docker Uses
| Resource | cgroup Controller | Docker Flag |
|---|---|---|
| CPU | cpu |
--cpus=1.5 |
| Memory | memory |
--memory=512m |
| Disk I/O | blkio |
--device-read-bps |
| PIDs | pids |
--pids-limit=100 |
Real Example: Memory Limit
docker run -d --name stress --memory=100m progrium/stress
docker exec stress stress --vm 1 --vm-bytes 200m
→ Container crashes with OOM (Out of Memory)
→ Host stays safe → no swap thrashing
How Docker Combines Them
+--------------------------------------------------+
| Docker Container |
| +-------------------------------------------+ |
| | App (nginx) | |
| | PID: 1 | |
| | IP: 172.17.0.3 | |
| | Files: /var/www (from image) | |
| +-------------------------------------------+ |
| ↑ ↑ |
| Namespaces cgroups |
| (isolation) (resource limits) |
+--------------------------------------------------+
↑ ↑
Linux Kernel Linux Kernel
Deep Dive: What Happens When You Run docker run nginx
| Step | Kernel Feature | What Happens |
|---|---|---|
| 1 | MNT namespace | Mounts overlay filesystem (image layers) |
| 2 | PID namespace | New PID space; nginx becomes PID 1 |
| 3 | NET namespace | Creates veth pair → container gets private IP |
| 4 | UTS namespace | Sets container hostname |
| 5 | User namespace | Maps root → unprivileged host UID |
| 6 | cgroup | Sets memory.limit=∞, cpu.shares=1024 |
| 7 | runc | Executes /bin/nginx in this isolated world |
Analogy: Hotel vs Docker
| Hotel Room | Docker Container |
|---|---|
| You see only your room | Namespaces |
| You get 1 lamp, 1 bed | cgroups |
| Key card opens only your door | Isolation |
| Hotel manager sets power limit | Resource control |
Why This Matters (Real-World Impact)
| Scenario | Without cgroups/namespaces | With Docker |
|---|---|---|
| Noisy neighbor app | Crashes entire server | Limited to 512MB RAM |
| Security breach | Attacker sees all processes | Sees only container |
| Port conflict | Two apps want port 80 | Each has own lo interface |
| Dev → Prod | "Works on my machine" | Identical environment |
# 1. Check container's view
docker run -it ubuntu bash
# → hostname, ip, ps → all isolated
# 2. On host, inspect cgroups
cat /sys/fs/cgroup/memory/$(docker inspect --format '{{.Id}}' <container>)/memory.limit_in_bytes
# → 9223372036854771712 (unlimited) or your --memory value
# 3. See network namespace
sudo nsenter -t $(docker inspect --format={{.State.Pid}} <container>) -n ip addr
# → Shows container's eth0@...
Summary: The Magic Formula
Docker Container =
Namespaces (6 types)
+ cgroups (resource limits)
+ OverlayFS (image layers)
+ Linux Kernel
No VM. No hypervisor. Just smart use of Linux.
Docker is not a VM. It's a process with superpowers.
Now you know why Docker is fast, secure, and consistent — it's all Linux kernel magic.
Next step: Try docker run --cpus=0.5 --memory=100m nginx and watch it get throttled!
3. Docker vs VMware (Virtual Machines)
| Feature | Docker Container | VMware VM |
|---|---|---|
| Size | 50–200 MB | 10–50 GB |
| Boot time | 1–2 seconds | 30–60 seconds |
| OS | Shares host kernel | Full guest OS |
| Isolation | Process-level (namespaces) | Hardware-level (hypervisor) |
| Performance | Near-native | 5–15% overhead |
| Density | 1000+ per host | 10–50 per host |
| Use case | Microservices, CI/CD | Different OS, legacy apps |
Docker = lightweight process
VM = heavy computer inside computer
4. Hypervisor vs Docker Engine
| Type | Hypervisor (Type 1/2) | Docker Engine |
|---|---|---|
| Examples | VMware ESXi, Hyper-V, VirtualBox | Docker, Podman |
| Runs on | Bare metal or host OS | Linux kernel |
| Isolates | Full machines | Processes |
| Needs | CPU virtualization (VT-x/AMD-V) | Just Linux kernel |
Docker does NOT use a hypervisor (except on Windows/Mac via tiny Linux VM).
5. Docker Desktop – What It Actually Does
Windows/Mac users → Your machine doesn’t have Linux kernel
Docker Desktop creates a tiny Linux VM (2–4GB) using:
- Windows → Hyper-V or WSL2
- Mac → HyperKit or Virtualization.framework
Your Mac/Windows
↓
Docker Desktop creates Linux VM
↓
Docker Engine runs inside it
↓
You run containers
Docker Desktop is NOT Docker – it’s just a launcher for non-Linux machines.
6. Basic Docker Commands (Must Know)
# Images
docker pull nginx # Download image
docker images # List images
docker build -t myapp . # Build from Dockerfile
# Containers
docker run nginx # Run container
docker run -d -p 8080:80 nginx # Detached + port mapping
docker ps # Running containers
docker ps -a # All containers
docker logs mycontainer # View logs
docker exec -it mycontainer bash # Enter container
# Cleanup
docker stop mycontainer
docker rm mycontainer
docker rmi nginx # Remove image
7. Docker Volumes – Persistent
"Where does my database data go?"
Containers are ephemeral → delete container = delete data
3 Ways to Persist Data
# 1. Bind mount (your folder)
docker run -v /home/user/data:/app/data nginx
# 2. Named volume (Docker manages)
docker volume create mydata
docker run -v mydata:/app/data postgres
# 3. Anonymous volume
docker run -v /app/data postgres
Volume Types Compared
| Type | Path Known? | Managed by Docker? | Use Case |
|---|---|---|---|
| Bind mount | Yes | No | Edit code live |
| Named volume | No | Yes | Databases |
| Anonymous | No | Yes | Temp data |
8. Docker Networks – How Containers Talk
By default, each container gets its own isolated network stack (thanks to Linux NET namespace).
→ Without configuration, containers cannot talk to each other or the outside world.
Docker provides 5 built-in network drivers to control how and when containers communicate.
The 5 Docker Network Types
| Network Type | Command | Isolation | External Access | Use Case |
|---|---|---|---|---|
bridge |
Default | Yes | With -p |
Most apps |
host |
--network host |
No | Direct | High perf |
none |
--network none |
Full | No | Security |
overlay |
Swarm mode | Yes | With ingress | Multi-host |
macvlan |
--network macvlan |
Yes | Direct IP | Legacy integration |
1. bridge – The Default & Most Used
- Creates a private internal network on the host (
172.17.0.0/16by default) - Containers get private IPs (
172.17.0.2,172.17.0.3) - Docker sets up NAT using
iptables→ port forwarding with-p
Host (192.168.1.100) Container
┌────────────────────┐ ┌──────────────┐
│ docker0 bridge │ ──veth──► │ eth0 │
│ 172.17.0.1 │ │ 172.17.0.2 │
└────────────────────┘ └──────────────┘
Custom Bridge Network (Best Practice)
docker network create --driver bridge mynet
docker run --network mynet --name db postgres
docker run --network mynet --name app myapp
→ app → db:5432
2. host – Zero Isolation, Max Performance
- Container shares the host’s network stack
- No NAT, no port mapping needed
- Container sees host’s IP and ports
docker run --network host nginx
→ nginx listens on host’s port 80 directly
Pros - Zero overhead (no NAT, no veth) - Full access to host interfaces - Ideal for monitoring agents, network tools
Cons - No isolation → security risk - Port conflicts → only one service per port - Not portable
Real-World Use
# Prometheus Node Exporter
docker run --network host --pid host prom/node-exporter
3. none – Total Isolation
docker run --network none alpine
What You Get
- Container has only
lo(loopback) - No IP, no DNS, no external access
Use Cases
- Security sandbox
- Offline jobs (data processing)
- Testing
docker run --network none myapp process-data /input /output
4. overlay – Multi-Host Networking (Docker Swarm)
- Creates a distributed network across Swarm nodes
- Uses VXLAN encapsulation
- Containers on different hosts can communicate
Node 1 Node 2
┌────────────┐ ┌────────────┐
│ web:10.0.0.2│ ◄──► │ db:10.0.0.3│
└────────────┘ └────────────┘
overlay network (10.0.0.0/24)
Setup
docker swarm init
docker network create --driver overlay mynet
docker service create --network mynet --name web nginx
Use Case - Microservices across 10 servers - Zero-downtime deployments
5. macvlan – Give Container Its Own MAC & IP
- Container gets a real IP on your physical network
- Bypasses Docker’s NAT
- Appears as a physical device
docker network create -d macvlan \
--subnet=192.168.1.0/24 \
--gateway=192.168.1.1 \
-o parent=eth0 my_macvlan
docker run --network my_macvlan --ip=192.168.1.100 myapp
Security Best Practices
| Rule | Why |
|---|---|
Use internal: true for DB networks |
Prevent external access |
Never use host in production |
Breaks isolation |
Use --network-alias for DNS |
api.service.local |
Limit with iptables |
Block unused ports |
Performance Comparison
| Network | Latency | Throughput | CPU Overhead |
|---|---|---|---|
bridge |
~0.1ms | High | Low |
host |
0ms | Max | None |
overlay |
~1–2ms | Medium | Medium |
macvlan |
~0.1ms | High | Low |
Why Port Forwarding (-p host:container) is Needed in Docker?
Because each container has its own private network (thanks to Linux NET namespace).
Without -p, the container’s port is only reachable inside the container — not from your host or the internet.
| Layer | Network View |
|---|---|
| Host (your laptop/server) | Has IP: 192.168.1.100, listens on port 80 |
| Container | Has private IP: 172.17.0.2, listens on port 80 |
→ Container’s 80 ≠ Host’s 80
→ They are in different network namespaces
Host Network Container Network
┌───────────────┐ ┌─────────────────┐
│ IP: 192.168.1.100 │ IP: 172.17.0.2 │
│ Port 80: [free] ─────►│ Port 80: [nginx] │
└───────────────┘ -p │ │
8080:80 │
└─────────────────┘
What -p 8080:80 Actually Does
docker run -p 8080:80 nginx
| Part | Meaning |
|---|---|
8080 |
Host port – what you type in browser |
80 |
Container port – where nginx is listening |
-p |
Tell Docker: "forward traffic from host:8080 → container:80" |
Docker uses iptables (or nftables) to set up NAT (Network Address Translation):
Incoming packet:
DST: 192.168.1.100:8080
↓ (DNAT)
DST: 172.17.0.2:80
↓
Reaches nginx → 200 OK
9. Docker Compose
docker-compose.yml = one file to rule them all
Sample docker compose file which deploys multiple services/containers running on multiple networks with mulitple volume mounts etc. Compose file
docker-compose up -d # Start everything
docker-compose down # Stop and remove
10. Docker Container vs Host: What Is Shared?
| Resource | Shared? | Details |
|---|---|---|
| Linux Kernel | Yes | Containers use the same kernel as the host (via namespaces) |
| CPU | Yes | Same physical CPU cores (time-sliced via cgroups) |
| Memory (RAM) | Yes | Same physical RAM (limited by cgroups) |
| Network Stack | Partially | Host kernel handles packets; container has virtual interfaces (veth) |
| Storage Driver | Yes | overlay2, aufs, etc. run on host kernel |
| Binaries & Libraries | No (unless bind mount) | Container has its own filesystem (from image) |
| Processes | No | Isolated via PID namespace |
| Filesystem | No (unless volume) | Container has its own root (/) |
| Users | No (unless --user) |
Isolated via user namespace |
| Devices | No (unless --device) |
GPU, USB, etc. blocked by default |
How Isolation Works (Even When Shared)
| Shared Resource | Isolation Mechanism |
|---|---|
| Kernel | Namespaces (PID, NET, MNT, UTS, IPC, User) |
| CPU/RAM | cgroups (limits usage) |
| Network | Network namespace + iptables/NAT |
| Filesystem | OverlayFS / UnionFS (image layers + writable top layer) |
Real Example: What You See
# On Host
uname -r
# → 5.15.0-100-generic
# Inside Container
docker run -it alpine uname -r
# → 5.15.0-100-generic ← SAME KERNEL!
# CPU is shared
htop # → See container processes using host CPU
What You Can Access from Container (by default)
| Resource | Accessible? | How |
|---|---|---|
| Host files | No | Only via -v /host/path:/container/path |
| Host processes | No | ps aux shows only container PIDs |
| Host network | No | ip a shows container IPs (172.17.x.x) |
| Host devices | No | Only with --device /dev/nvidia0 |
Bottom Line:
Docker containers share the host kernel and hardware — but are isolated at the process, network, and filesystem level.
It’s not a VM — it’s a securely jailed process.
Summary Cheat Sheet
| Concept | One-Liner |
|---|---|
| Docker | Run apps in isolated, portable boxes |
| Docker Daemon | Background service that manages everything |
| Docker Desktop | GUI + Linux VM for Windows/Mac |
| VM vs Docker | VM = full OS, Docker = just app |
| Hypervisor | VMware/Hyper-V (not used by Docker) |
| Volumes | Persist data outside containers |
| Networks | Let containers talk to each other |
| Docker Compose | docker-compose up = full app stack |
Master these 8 concepts → you’re a Docker pro.
Now go run:
docker run -d -p 8080:80 nginx
And open http://localhost:8080 – welcome to the future!
Docker Images
A Docker image is a read-only template that contains: - Your app code - Dependencies (libraries, binaries) - OS filesystem (just enough to run the app) - Configuration (env vars, entrypoint)
Think of it as "a frozen, executable snapshot of your app".
Your App (Python, Node.js, Java)
↓
Docker Image = App + OS + Dependencies
↓
Container = Running instance of the image
2. Image vs Container – The Key Difference
| Feature | Docker Image | Docker Container |
|---|---|---|
| State | Read-only (immutable) | Writable (running instance) |
| Lifecycle | Created once, reused | Created from image, can be stopped |
| Storage | Layers in /var/lib/docker |
Adds a writable layer on top |
| Example | nginx:1.25 |
docker run nginx:1.25 → container |
| Analogy | Blu-ray disc | DVD player playing the movie |
docker images # List images (Blu-rays)
docker ps # List running containers (players)
3. How Images Are Built – The Dockerfile
Dockerfile = recipe to build an image
# Dockerfile
FROM node:18-alpine # Base image
WORKDIR /app # Set working directory
COPY package*.json . # Copy dependency files
RUN npm ci --only=production # Install deps
COPY . . # Copy app code
EXPOSE 3000 # Document port
CMD ["node", "server.js"] # Default command
4. Dockerfile Commands – Full Breakdown
| Command | What It Does | Example |
|---|---|---|
FROM |
Base image – start from here | FROM ubuntu:22.04 |
WORKDIR |
Set working directory | WORKDIR /app |
COPY |
Copy files from host → image | COPY . . |
ADD |
Like COPY, but can untar or fetch URLs |
ADD app.tar.gz /app/ |
RUN |
Execute command during build | RUN apt update && apt install -y curl |
CMD |
Default command when container starts | CMD ["python", "app.py"] |
ENTRYPOINT |
Fixed command (hard to override) | ENTRYPOINT ["nginx"] |
ENV |
Set environment variable | ENV NODE_ENV=production |
EXPOSE |
Document port (does NOT publish) | EXPOSE 8080 |
VOLUME |
Declare persistent storage | VOLUME /data |
USER |
Run as specific user | USER node |
LABEL |
Add metadata | LABEL maintainer="you@example.com" |
ARG |
Build-time variable | ARG VERSION=1.0 |
5. Image Layers – How Docker Saves Space
Every Dockerfile instruction = one layer
Layer 1: FROM node:18-alpine
Layer 2: WORKDIR /app
Layer 3: COPY package*.json .
Layer 4: RUN npm ci
Layer 5: COPY . .
Layer 6: CMD ["node", "server.js"]
Layer Caching (The Magic)
docker build -t myapp .
- Docker caches unchanged layers
- If you edit
server.js→ only Layer 5 & 6 rebuild - If you edit
package.json→ Layer 4, 5, 6 rebuild
Best Practice: Put rarely changing steps early
# GOOD: Install deps first
COPY package*.json .
RUN npm ci
COPY . .
# BAD: Copy code first
COPY . .
RUN npm ci # Re-runs on every code change!
6. How to Optimize Docker Images
1. Use Small Base Images
# 1.8 GB → 80 MB
FROM ubuntu:22.04 # BAD
FROM alpine:3.19 # GOOD
FROM node:18-alpine # BEST for Node.js
2. Multi-Stage Builds (Production Secret)
Multi-stage Docker build optimizes images by:
- Separating build and runtime
- Stage 1 (Build): Use heavy tools (compilers, SDKs)
-
Stage 2 (Runtime): Use tiny base (e.g.,
alpine) — no build tools -
Copy only needed files
dockerfile COPY --from=builder /app/dist /app→ Final image has only compiled binaries, not source or build deps -
Dramatically reduces size
Before: 900 MB (Node.js + source + npm) After: ~80 MB (just compiled JS + runtime) -
Improves security & speed
- No unused tools = smaller attack surface
- Faster pull/push/scans
Result: 10x smaller, faster, safer images — production standard.
# Stage 1: Build
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json .
RUN npm ci
COPY . .
RUN npm run build
# Stage 2: Run
FROM alpine:3.19
RUN apk add --no-cache nodejs
COPY --from=builder /app/dist /app
CMD ["node", "/app/server.js"]
→ Final image: ~50 MB (no build tools!)
3. .dockerignore (Like .gitignore)
node_modules
.git
*.log
dist
→ Speeds up COPY, reduces image size
4. Combine RUN Commands
# BAD: 3 layers
RUN apt update
RUN apt install -y curl
RUN rm -rf /var/lib/apt/lists/*
# GOOD: 1 layer
RUN apt update && \
apt install -y curl && \
rm -rf /var/lib/apt/lists/*
8. Image Tagging & Registry
# Tag image
docker build -t myapp:1.0 .
docker tag myapp:1.0 myusername/myapp:1.0
# Push to Docker Hub
docker push myusername/myapp:1.0
# Pull from anywhere
docker pull myusername/myapp:1.0
9. Fundamental Concepts
| Concept | Explanation |
|---|---|
| Image ID | Unique hash (sha256:abc123...) |
| Registry | Docker Hub, GitHub Container Registry, AWS ECR |
| Repository | Collection of tagged images (nginx, nginx:1.25) |
| Layer Deduplication | Same layer (e.g., alpine:3.19) shared across images |
| Union Filesystem | overlay2 merges layers into one filesystem |
| Image Squashing | docker squash (experimental) → flatten layers |
10. Commands Cheat Sheet
# Build
docker build -t myapp:1.0 .
# List images
docker images
# Inspect image
docker inspect myapp:1.0
# View layers
docker history myapp:1.0
# Remove image
docker rmi myapp:1.0
# Save/load image (offline)
docker save myapp:1.0 > myapp.tar
docker load < myapp.tar
Summary Table
| Concept | Key Point |
|---|---|
| Image | Read-only template |
| Container | Running, writable instance |
| Dockerfile | Recipe to build image |
| Layers | Each command = 1 layer |
| Caching | Unchanged layers reused |
| Multi-stage | Build → discard tools → tiny image |
| Optimization | Small base + .dockerignore + combine RUN |
Golden Rule:
"Build once, run anywhere"
One image = runs identically on your laptop, AWS, GCP, Azure, or your friend’s PC.