Containers vs Virtual Machines
Virtual Machine (VM): Emulates hardware. Runs a full Guest OS on top of a Hypervisor. Heavy (GBs). Container: Shares a kernel with its host environment. Image size and startup time depend on the application; Linux containers on a non-Linux desktop normally run inside a Linux VM.
Containers are process isolation mechanisms provided by the Linux Kernel.
The Three Pillars of Containers
How does docker run actually work? It uses three Linux features:
1. Namespaces (Isolation)
Namespaces limit what a process can see.
| Namespace | What it Isolates |
|---|---|
| PID | Process identifiers. The first process in a new PID namespace has PID 1 there. |
| NET | Network Interfaces (eth0), Ports, Routing Tables. |
| MNT | Mount Points. Container has its own root /. |
| UTS | Hostname and Domain. |
| IPC | Inter-Process Communication (Shared Memory). |
| USER | User/group ID mappings, when a user namespace is configured. Container root is not automatically remapped by every configuration. |
Try it: unshare --fork --pid --mount-proc /bin/bash creates a new PID namespace.
2. Control Groups (Cgroups) (Resource Limiting)
Cgroups limit how much a process can use.
- Memory: "You get 512MB RAM." (
OOM Killif exceeded) - CPU: "You get 0.5 CPU cores."
- Block I/O: "Limit disk read speed to 10MB/s."
Accessed via file system: /sys/fs/cgroup/.
3. Union Filesystem (Storage)
How are images so efficient?
Docker Images are built from Layers.
- Layers: Read-only changesets (e.g., Ubuntu Base -> Add Python -> Add App Code).
- Union Mount: Merges all layers into a single view.
- Copy-on-Write (COW): If a container modifies a file from a lower layer, it copies it up to the writable top layer first.
OverlayFS is a common Linux implementation; the configured storage driver or snapshotter determines the actual behavior.
Docker Architecture
Client-Server model.
- Docker CLI: Sends commands (
docker run) to Daemon via REST API (Unix Socket). - Docker Daemon (
dockerd): Manages objects (images, containers, networks). - Containerd: High-level runtime. Manages image pull/push and lifecycle.
- Runc: Low-level runtime. Spawns the actual process using syscalls.
Building Efficient Images
1. Multi-Stage Builds
Build in a fat image (Go compiler), copy binary to tiny image (Alpine/Scratch).
# Stage 1: Build FROM golang:alpine AS builder WORKDIR /app COPY . . RUN CGO_ENABLED=0 go build -trimpath -o /myapp main.go # Stage 2: Run FROM scratch COPY --from=builder /myapp /myapp USER 65532:65532 ENTRYPOINT ["/myapp"]
The sample assumes a pure-Go program that needs no external files, certificates or dynamic libraries. A real build should pin maintained base images by version/digest and include any runtime assets. Smaller final images are the purpose of multi-stage builds, not a guaranteed size reduction.
2. Layer Caching
Order matters! Put frequently changing instructions (COPY code) at the bottom.
# BAD: Breaks cache if source code changes COPY . . RUN npm install # GOOD: Caches dependencies unless package.json changes COPY package.json package-lock.json ./ RUN npm ci COPY . .
Security: Container Breakout
If a container runs as root, and a vulnerability exists in the Kernel...
Container Breakout: Attacker escapes the container and gains access to the Host.
Defenses:
- Run as Non-Root:
USER appuserin Dockerfile. - Read-Only Root:
docker run --read-only. - Capabilities: Drop unused Linux privileges (
--cap-drop ALL --cap-add NET_BIND_SERVICE).
Worked decision: an image that builds but will not start
A binary copied from a glibc-based builder into Alpine can fail because the expected dynamic loader is absent. Before blaming container networking, inspect the binary’s dependencies and target architecture. For a pure-Go example, disabling CGO makes a scratch image possible; an application that uses a native database driver needs a compatible runtime instead. Test the final image as its non-root user, including certificate loading and writable-directory requirements.
Related Concepts
- Kubernetes Architecture (Orchestrates containers)
- CI/CD Pipelines (Builds images)
- Microservices (Deployed in containers)
Keep reading
Kubernetes Architecture Explained
Under the hood of K8s: The Control Plane (API Server, Scheduler, Etcd, Controllers) and Data Plane (Kubelet, Kube-proxy, Container Runtime).
Blue-Green Deployment: Traffic Switching and State Compatibility
Plan a two-environment release with capacity headroom, connection draining, compatible schema changes and a testable rollback window.
CI/CD Pipeline Architecture
Designing robust Continuous Integration and Continuous Deployment pipelines. Strategies for artifact promotion, testing pyramids, canary deployments, and rollback mechanisms.
The Observability Stack
Moving beyond simple monitoring. How to build a full observability stack using the Three Pillars: Logs, Metrics, and Distributed Tracing.
Distributed Tracing
Complete guide to tracking requests across microservices using distributed tracing, covering trace context propagation, span instrumentation, OpenTelemetry implementation, and production debugging with Jaeger, Zipkin, and Datadog APM.
Concurrency Patterns
Essential patterns for managing concurrent execution. Mutex, Semaphores, Monitors, and modern approaches like the Actor Model and CSP.