Chapter 3: Containers

Containers Deep Dive

Understanding how Containers work - Docker, container images, namespaces, cgroups, and the technology that revolutionized application deployment.

Containers Deep Dive

Containers have revolutionized how we deploy applications. Let's understand how they work at a fundamental level.

What Makes a Container?

A container is NOT a virtual machine. It's a combination of Linux kernel features that provide isolation:

  1. Namespaces - Isolate what processes can see
  2. Cgroups - Limit what resources processes can use
  3. Union Filesystems - Layer filesystems efficiently

Linux Namespaces

Namespaces create isolated views of system resources. Each container gets its own namespace for:

Namespace Isolates
PID Process IDs - Container sees its processes starting from PID 1
Network Network interfaces, IP addresses, ports
Mount Filesystem mount points
UTS Hostname and domain name
IPC Inter-process communication
User User and group IDs
# On host: thousands of processes
$ ps aux | wc -l
247

# Inside container: only container processes
$ docker exec mycontainer ps aux
PID   USER     COMMAND
1     root     /app/server
12    root     ps aux

Control Groups (cgroups)

Cgroups limit and account for resource usage:

Container Resource Limits:
  CPU: 2 cores (or 200% CPU time)
  Memory: 4GB (hard limit)
  Memory Swap: 8GB
  Disk I/O: 100 MB/s

When a container exceeds its memory limit, it gets killed (OOMKilled).

Container Images

A container image is a read-only template containing:

  • Application code
  • Runtime (Python, Node.js, etc.)
  • Libraries and dependencies
  • Environment configuration

Image Layers

Images are built in layers, each layer representing a change:

FROM python:3.11-slim     # Layer 1: Base Python image
COPY requirements.txt .   # Layer 2: Add requirements file
RUN pip install -r req... # Layer 3: Install dependencies
COPY . /app               # Layer 4: Add application code
CMD ["python", "app.py"]  # Layer 5: Define startup command
┌─────────────────────────┐
│  Layer 5: CMD           │  (Read-only)
├─────────────────────────┤
│  Layer 4: App code      │  (Read-only)
├─────────────────────────┤
│  Layer 3: Dependencies  │  (Read-only)
├─────────────────────────┤
│  Layer 2: requirements  │  (Read-only)
├─────────────────────────┤
│  Layer 1: Base image    │  (Read-only)
└─────────────────────────┘

Copy-on-Write

When a container runs, a thin writable layer is added on top:

┌─────────────────────────┐
│  Writable Container     │  ← Changes go here
│  Layer                  │
├─────────────────────────┤
│  Image Layers           │  (Read-only, shared)
│  (shared between        │
│   containers)           │
└─────────────────────────┘

This is why containers are so efficient - 100 containers from the same image share the base layers!

Docker Architecture

┌──────────────────────────────────────────┐
│              Docker CLI                   │
│         (docker run, build, etc.)        │
└────────────────────┬─────────────────────┘
                     │ REST API
┌────────────────────┴─────────────────────┐
│            Docker Daemon                  │
│         (dockerd - background service)    │
├──────────────────────────────────────────┤
│            containerd                     │
│      (container lifecycle management)     │
├──────────────────────────────────────────┤
│              runc                         │
│     (OCI runtime - creates containers)    │
└──────────────────────────────────────────┘

Container Lifecycle

# Create and start
docker run -d --name myapp nginx

# Lifecycle states
Created → Running → Paused → Running → Stopped → Removed
            ↑                   ↓
            └───────────────────┘
                  (restart)

Container Networking

Containers can be connected in various ways:

Bridge Network (Default)

┌─────────────────────────────────────┐
│           Docker Bridge              │
│         (172.17.0.0/16)             │
├─────────┬─────────┬─────────────────┤
│ Container│Container│                │
│  .2      │   .3    │                │
└─────────┴─────────┴─────────────────┘

Host Network

Container uses the host's network directly (best performance).

Custom Networks

Create isolated networks for specific applications.

Container Storage

  • Volumes: Persistent storage managed by Docker
  • Bind Mounts: Map host directory into container
  • tmpfs: In-memory storage (fast but temporary)
# Volume example
docker run -v mydata:/app/data myimage

# Bind mount example
docker run -v /host/path:/container/path myimage

Advantages of Containers

  1. Lightweight - Share kernel, MBs not GBs
  2. Fast Startup - Seconds, not minutes
  3. Portable - "Works on my machine" solved
  4. Efficient - Higher density than VMs
  5. Immutable - Same image runs everywhere
  6. Version Control - Images can be tagged and tracked

Disadvantages of Containers

  1. Same Kernel - Can only run Linux containers on Linux
  2. Security - Weaker isolation than VMs
  3. Stateful Apps - More complex to handle persistent data
  4. Networking - Can be complex to configure
  5. Monitoring - Need container-aware tools

Container Orchestration

For production, you need orchestration:

  • Kubernetes - Industry standard
  • Docker Swarm - Simple, built into Docker
  • Nomad - HashiCorp's alternative

These handle:

  • Scheduling containers across hosts
  • Service discovery
  • Load balancing
  • Rolling updates
  • Self-healing

What's Next?

Now that you understand both VMs and Containers in depth, let's compare them side-by-side in the next chapter.