Docker 1 ๐ณ Containerization Fundamentals: Containers vs Virtual Machines (VMs)
Containerization is the practice of packaging an application together with all its dependencies โ libraries, runtime, system tools, and configuration โ into a single, portable unit that can run consistently across any environment. Docker, released in 2013, did not invent this concept, but it standardized the tooling and workflow in a way that made containers practical for everyday developers . The core insight behind Docker is simple: rather than shipping your application alone and hoping the target machine has the right dependencies, you ship the application and its entire user-space environment as one immutable artifact.
The distinction between containers and virtual machines is the foundation on which everything else in Docker rests. Both provide isolation and portability, but they achieve these goals through fundamentally different mechanisms. A virtual machine simulates an entire computer, including its own operating system kernel. A container, by contrast, uses the host’s kernel and isolates only the application layer. This architectural difference produces dramatic consequences for resource usage, startup time, security boundaries, and operational complexity.
This chapter covers the architectural foundations of containers, how they differ from virtual machines at the kernel level, the Linux primitives that make containerization possible, and the practical trade-offs between the two approaches. We will examine namespaces, cgroups, and the layered image model that together form the technical basis of Docker.
Key point: Containers share the host operating system’s kernel and isolate only the application layer, while virtual machines run a complete guest operating system with its own kernel. This makes containers lightweight and fast but gives VMs stronger isolation boundaries.
Why containers exist alongside virtual machines
The reproducibility problem. Software that works on a developer’s laptop often fails in testing or production because the environments differ โ different library versions, different system configurations, different operating system patches. Virtual machines solved this by shipping an entire operating system, but at a cost: each VM carries gigabytes of OS files and consumes dedicated CPU and memory even when idle. Containers address the same problem by packaging only the application and its user-space dependencies, sharing the host kernel and its resource pool . The result is a unit of deployment that is both reproducible and economical.
The resource density problem. A typical virtual machine requires pre-allocated CPU, memory, and storage, much of which sits idle. Running twenty VMs on a server means twenty complete operating systems, each consuming memory for kernel structures, device drivers, and background services. Containers eliminate this duplication by sharing a single kernel. Each container adds only the memory footprint of its application processes and the filesystem layers it needs. This allows an order of magnitude more workloads to run on the same hardware .
The startup latency problem. Booting a virtual machine involves loading a kernel, initializing device drivers, starting system services, and reaching a usable state โ a process measured in tens of seconds to minutes. Starting a container involves creating a process with a new set of namespaces and a filesystem view, a process measured in milliseconds . This speed difference changes the economics of scaling: containers can be started and stopped rapidly in response to load, while VMs must be provisioned ahead of time.
The packaging and distribution problem. Virtual machine images are large โ often tens of gigabytes โ and are stored in proprietary formats that vary by hypervisor. Docker images are built from layers, each layer representing a filesystem change. Because layers are shared between images, pulling a new image often downloads only the changed layers. The public Docker Hub registry provides a vast ecosystem of pre-built images that developers can pull and run immediately .
The security trade-off problem. Containers provide weaker isolation than virtual machines because they share the host kernel. A kernel vulnerability can potentially be exploited to escape a container and access the host or other containers. Virtual machines, with their separate kernels, present a stronger security boundary. This trade-off is deliberate: containers choose efficiency and speed over the strongest possible isolation, and technologies like user namespaces and seccomp profiles mitigate the risk .
a. The architectural difference: shared kernel vs guest kernel
The fundamental architectural distinction between containers and virtual machines lies in how they interact with the operating system kernel. A virtual machine runs its own kernel. The hypervisor โ software like VMware ESXi, Microsoft Hyper-V, or KVM โ emulates hardware devices and provides a platform on which a complete operating system can boot. That guest OS manages its own memory, schedules its own processes, and runs its own device drivers. The host OS and the guest OS are independent; they share only the physical hardware .
A container does not have its own kernel. It runs as a set of processes on the host operating system, isolated from other processes through kernel features. The container’s filesystem is constructed from image layers, but the kernel that executes its code is the host’s kernel. This means a container cannot run a different operating system kernel than its host โ a Linux container requires a Linux host, and a Windows container requires a Windows host (or a Linux host running a Windows VM) .
This difference has cascading consequences. Because the container shares the kernel, it cannot load kernel modules, cannot have its own device drivers, and cannot run a different kernel version than the host. But in exchange, it avoids the overhead of duplicating the kernel and its associated data structures in memory. The kernel’s page cache, network stack, and scheduler are shared among all containers on the host, leading to more efficient resource utilization .
b. Linux namespaces: the isolation mechanism
Containers achieve isolation through Linux namespaces, a kernel feature that partitions global system resources so that processes in different namespaces see different views of the system. A namespace wraps a global resource in an abstraction that makes it appear to the processes within the namespace that they have their own isolated instance of that resource .
Linux provides several types of namespaces, each isolating a different category of system resource. The PID namespace gives processes their own process ID number space: the first process in a new PID namespace has PID 1, just as init does on a normal system, and processes in different PID namespaces cannot see or signal each other . The mount namespace isolates filesystem mount points, allowing a container to have its own root filesystem view . The network namespace isolates network devices, IP addresses, routing tables, and port numbers, so containers can bind to the same port without conflict . The UTS namespace isolates the hostname and domain name . The user namespace isolates user and group IDs, allowing a process to have root privileges inside the namespace while being unprivileged outside it . The IPC namespace isolates inter-process communication resources like message queues and shared memory . The cgroup namespace isolates the cgroup root directory view .
When Docker starts a container, it creates a new set of these namespaces for the container’s processes. The processes inside see only the resources within their namespaces. From the host, the container’s processes are visible and manageable, but from inside, the container appears as a self-contained system .
c. Control groups: resource limits and accounting
Namespaces isolate what processes can see. Control groups, or cgroups, control how much of a resource they can use. A cgroup is a kernel feature that limits, accounts for, and isolates the resource usage โ CPU, memory, disk I/O, network bandwidth โ of a collection of processes .
Docker places each container into its own cgroup. This ensures that one container cannot monopolize the host’s CPU, exhaust its memory, or saturate its disk bandwidth, starving other containers or the host itself . System administrators can set hard limits: a container might be restricted to 2 CPU cores, 512 MB of memory, and 100 MB/s of disk throughput. These limits are enforced by the kernel, not by cooperation from the container .
Cgroups also provide accounting: the kernel tracks how much CPU time, memory, and I/O each cgroup consumes. This data feeds into monitoring tools and orchestration systems, enabling automatic scaling and resource optimization . The combination of namespaces and cgroups is what makes containers viable: namespaces provide the illusion of isolation, and cgroups ensure that the illusion does not collapse under resource contention.
Complete Example Session
# ============================================
# PART 1: CHECK DOCKER INSTALLATION
# ============================================
# Verify that Docker is available and running.
docker --version
docker info
# ============================================
# PART 2: RUN A SIMPLE CONTAINER
# ============================================
# Pull and run a minimal Linux container.
docker run hello-world
# ============================================
# PART 3: INTERACT WITH A CONTAINER
# ============================================
# Start an interactive shell inside Ubuntu.
docker run -it ubuntu:latest /bin/bash
# ============================================
# PART 4: LIST CONTAINERS AND IMAGES
# ============================================
# See what is running and what is stored locally.
docker ps
docker ps -a
docker images
# ============================================
# PART 5: INSPECT A CONTAINER
# ============================================
# Examine the configuration and state of a container.
docker inspect <container_id>
# ============================================
# PART 6: LIMIT RESOURCES
# ============================================
# Run a container with CPU and memory limits.
docker run --cpus="1.5" --memory="512m" nginx
# ============================================
# PART 7: VIEW NAMESPACES
# ============================================
# On the host, inspect the namespaces of a
# running container process.
ls -l /proc/<pid>/ns/
# ============================================
# PART 8: VIEW CGROUPS
# ============================================
# Examine the cgroup of a running container.
cat /proc/<pid>/cgroup
# ============================================
# PART 9: BUILD A CUSTOM IMAGE
# ============================================
# Create a Dockerfile and build an image.
cat > Dockerfile << 'EOF'
FROM alpine:latest
RUN apk add --no-cache curl
CMD ["curl", "--version"]
EOF
docker build -t my-curl .
docker run my-curl
# ============================================
# PART 10: COMPARE CONTAINER AND VM
# ============================================
# Start a VM (if available) and compare startup
# time and resource usage with a container.
time docker run --rm alpine echo "container"
time vagrant up # if vagrant and a box are available
These ten parts move from verifying the Docker installation through running, inspecting, and limiting containers, to examining the underlying namespace and cgroup primitives, building a custom image, and comparing container behavior with a virtual machine. Each step reveals a layer of the containerization stack.
Quick Reference
Containers vs Virtual Machines
| Aspect | Container | Virtual Machine |
|---|---|---|
| Kernel | Shares host kernel | Runs own guest kernel |
| Operating system | User-space portion only | Complete OS including kernel |
| Startup time | Milliseconds to seconds | Seconds to minutes |
| Size | Megabytes | Gigabytes |
| Resource overhead | Minimal | Significant |
| Isolation strength | Lightweight | Strong hardware-level |
| Density | High (dozens per host) | Low (single digits per host) |
| Portability | High, OCI standard | Lower, hypervisor-specific |
Linux Namespace Types
| Namespace | Isolates |
|---|---|
| PID | Process IDs |
| Mount | Filesystem mount points |
| Network | Network devices, stacks, ports |
| UTS | Hostname and domain name |
| User | User and group IDs |
| IPC | Inter-process communication |
| Cgroup | Cgroup root directory |
| Time | Boot and monotonic clocks |
Docker Core Concepts
| Concept | Description |
|---|---|
| Image | Read-only template with application and dependencies |
| Container | Runnable instance of an image |
| Layer | Filesystem change in an image |
| Dockerfile | Text file with build instructions |
| Registry | Repository for storing and sharing images |
| Volume | Persistent storage for containers |
Best Practices
โ Do This:
FROM alpine:3.19 # Pin base image version
RUN apk add --no-cache curl # Minimize layers, no cache
COPY app /app # Copy application files
CMD ["/app/run.sh"] # Exec form for proper signals
โ Don’t Do This:
FROM alpine:latest # โ Unpinned base image
RUN apk update && apk add curl # โ Leaves cache, larger image
ADD http://example.com/file /app/ # โ Remote URL in ADD
CMD /app/run.sh # โ Shell form, poor signal handling
Common Pitfalls
| Pitfall | Why It Happens | Fix |
|---|---|---|
| Container exits immediately | No long-running process in CMD | Ensure CMD runs a foreground process |
| Data lost on container removal | Container filesystem is ephemeral | Use volumes for persistent data |
| Port not accessible | Container network is isolated | Publish ports with -p host:container |
| Permission denied on volume | Host and container UID mismatch | Match UIDs or use user namespaces |
| Image too large | Unnecessary files in layers | Use multi-stage builds and .dockerignore |
| OOM killed | Container memory limit exceeded | Increase limit or optimize application |
Real-World Examples
1. Development Environment
docker run -it --rm -v $(pwd):/workspace -w /workspace node:20 bash
2. Web Server with Volume
docker run -d -p 8080:80 -v ./html:/usr/share/nginx/html nginx
3. Database with Persistent Storage
docker run -d --name db -v pgdata:/var/lib/postgresql/data postgres:16
4. Resource-Constrained Service
docker run -d --cpus="0.5" --memory="256m" redis:7
5. Multi-Container Application
# docker-compose.yml
services:
web:
build: .
ports: ["8000:8000"]
db:
image: postgres:16
6. CI Build Step
docker run --rm -v $(pwd):/src -w /src golang:1.22 go test ./...
7. Kubernetes Pod
apiVersion: v1
kind: Pod
metadata:
name: web
spec:
containers:
- name: nginx
image: nginx:1.25
8. Container with Healthcheck
HEALTHCHECK --interval=30s --timeout=3s \
CMD curl -f http://localhost/ || exit 1
9. Multi-Stage Build
FROM golang:1.22 AS builder
WORKDIR /src
COPY . .
RUN go build -o /app
FROM alpine:3.19
COPY --from=builder /app /app
CMD ["/app"]
10. Docker Compose with Environment
services:
app:
image: myapp
environment:
- DATABASE_URL=postgres://db:5432/mydb
Visual
Container vs Virtual Machine Architecture
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ CONTAINER ARCHITECTURE VM ARCHITECTURE โ
โ โ
โ โโโโโโโโโโโ โโโโโโโโโโโ โโโโโโโโโโโ โโโโโโโโโโโ โ
โ โ App A โ โ App B โ โ App A โ โ App B โ โ
โ โโโโโโโโโโโค โโโโโโโโโโโค โโโโโโโโโโโค โโโโโโโโโโโค โ
โ โ Libs โ โ Libs โ โ Libs โ โ Libs โ โ
โ โโโโโโโโโโโค โโโโโโโโโโโค โโโโโโโโโโโค โโโโโโโโโโโค โ
โ โ โ โ โ โ Guest โ โ Guest โ โ
โ โ โ โ โ โ OS + โ โ OS + โ โ
โ โ โ โ โ โ Kernel โ โ Kernel โ โ
โ โโโโโโโโโโโ โโโโโโโโโโโ โโโโโโโโโโโ โโโโโโโโโโโ โ
โ โ โ โ โ โ
โ โโโโโโโฌโโโโโโ โโโโโโโฌโโโโโโ โ
โ โ โ โ
โ โโโโโโโโโโโโดโโโโโโโโโโโ โโโโโโโโโโโโดโโโโโโโโโโโ โ
โ โ HOST KERNEL โ โ HYPERVISOR โ โ
โ โ (shared) โ โ (VMware, KVM) โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ โ โ
โ โโโโโโโโโโโโดโโโโโโโโโโโ โโโโโโโโโโโโดโโโโโโโโโโโ โ
โ โ HOST OS โ โ HOST OS โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ โ โ
โ โโโโโโโโโโโโดโโโโโโโโโโโ โโโโโโโโโโโโดโโโโโโโโโโโ โ
โ โ HARDWARE โ โ HARDWARE โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โ Containers share the host kernel. VMs run a full guest OS โ
โ with its own kernel. This is the fundamental difference. โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Namespace Isolation
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ LINUX NAMESPACES IN A CONTAINER โ
โ โ
โ HOST VIEW: โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ PID 1000 (container init, PID 1 inside) โ โ
โ โ PID 1001 (container process, PID 2 inside) โ โ
โ โ PID 1002 (container process, PID 3 inside) โ โ
โ โ PID 500 (host process, not visible inside) โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โ CONTAINER VIEW (PID namespace): โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ PID 1 (container init) โ โ
โ โ PID 2 โ โ
โ โ PID 3 โ โ
โ โ (host PID 500 is invisible) โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โ Each namespace type isolates a different resource view. โ
โ Network: container sees its own eth0, IP, ports. โ
โ Mount: container sees its own root filesystem. โ
โ User: root inside = unprivileged outside. โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Docker Image Layers
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ IMAGE LAYERS ARE READ-ONLY AND SHARED โ
โ โ
โ Image: myapp:latest โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ Layer 4: COPY app /app (read-only) โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค โ
โ โ Layer 3: RUN apk add curl (read-only) โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค โ
โ โ Layer 2: RUN apk update (read-only) โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค โ
โ โ Layer 1: FROM alpine:3.19 (read-only) โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โ Container adds a thin writable layer on top: โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ Writable Layer (container-specific changes) โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค โ
โ โ Read-only layers (shared with other containers) โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โ Multiple containers using the same base image share โ
โ the read-only layers, saving disk space and memory. โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Namespaces and Cgroups Working Together
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ HOW CONTAINERS COMBINE NAMESPACES AND CGROUPS โ
โ โ
โ NAMESPACES (what the process SEES): โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ PID โ sees only its own processes โ โ
โ โ NET โ sees only its own network interfaces โ โ
โ โ MNT โ sees only its own filesystem tree โ โ
โ โ USER โ sees its own user/group IDs โ โ
โ โ UTS โ sees its own hostname โ โ
โ โ IPC โ sees only its own IPC resources โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โ CGROUPS (how much the process can USE): โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ CPU โ limited to N cores or percentage โ โ
โ โ Memory โ limited to M megabytes โ โ
โ โ I/O โ limited to disk bandwidth/IOPS โ โ
โ โ Tasks โ limited to N processes/threads โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โ Namespaces provide isolation. Cgroups provide containment. โ
โ Together they make a container: isolated and bounded. โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Summary
| Item | Value |
|---|---|
| Container definition | Isolated process with its own filesystem, network, and resource limits |
| Key difference from VM | Containers share host kernel; VMs run full guest OS |
| Namespaces | Kernel feature for isolating resource views |
| Namespace types | PID, Mount, Network, UTS, User, IPC, Cgroup, Time |
| Cgroups | Kernel feature for limiting and accounting resource usage |
| Container image | Read-only layered filesystem template |
| Container instance | Writable layer on top of image layers |
| Startup time | Containers in milliseconds; VMs in seconds to minutes |
| Isolation strength | VMs stronger; containers lighter |
| Density | Containers higher; VMs lower |
Key takeaways:
- Containers share the host kernel. A container does not run its own operating system kernel; it isolates processes using kernel namespaces and cgroups. This is the root of every other difference from VMs.
- Namespaces isolate resource views. PID, network, mount, user, UTS, IPC, cgroup, and time namespaces give each container its own view of system resources.
- Cgroups limit and account for resource usage. Without cgroups, a single container could starve other containers and the host of CPU, memory, or I/O.
- Containers are lightweight and fast. No kernel boot, no device initialization, no system service startup โ just process creation with new namespaces.
- Virtual machines provide stronger isolation. A separate kernel means a vulnerability in the guest kernel does not automatically compromise the host.
- Images are built from layers. Each Dockerfile instruction creates a read-only layer; containers add a thin writable layer on top.
- The trade-off is efficiency versus isolation. Containers win on density, speed, and portability; VMs win on security boundaries and guest OS compatibility.
Remember: Docker containers and virtual machines solve the same problem โ isolating applications and making them portable โ but they solve it at different layers of the stack. A virtual machine virtualizes hardware, allowing a complete operating system to run as if it were on its own computer. A container virtualizes the operating system itself, sharing the kernel while isolating the application’s view of the system. This architectural choice makes containers dramatically lighter and faster, at the cost of a weaker security boundary. Understanding namespaces and cgroups is understanding how containers actually work beneath the Docker command line. Every docker run creates a set of namespaces and a cgroup, and every container image is a stack of filesystem layers waiting to be instantiated as an isolated process tree.
Stop using slow, ad-bloated tool sites! ๐คฎ
๐ Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
โ
Finance (Mortgage, Interest, Inflation)
โ
Tech (Base64, JSON, Dev Suite, IP)
โ
Health (BMI, BMR, TDEE)
โ
Productivity (Timer, Workspace, QR)
โก๏ธ Fast & Private
๐ No data leaves your device
๐ 100% Free
๐ Use it now: https://tools.kandz.me
๐ Bookmark itโyouโll need it later!