Docker Container Architecture: Images, Volumes and Networks
What actually happens when you run a Docker container? A clear look at images, container filesystems, persistent volumes, and bridge networking on Linux.
Describing software containers as "lightweight virtual machines" is an enduring mischaracterization that leads developers down erroneous debugging paths. A container does not boot a guest operating system, emulate motherboard chipsets, or run an independent kernel hypervisor. In reality, a container is simply an ordinary Linux host process confined by kernel namespaces, restricted by control groups, and backed by a layered union filesystem. Peeling back these runtime abstractions reveals how Docker isolates code securely and executes workloads with bare-metal speed.
Architecture Quick Summary
Docker relies on a modular stack: dockerd handles client API commands; containerd oversees image transfers and container execution; and runc interacts with the Linux kernel to configure namespaces and cgroups. Storage utilizes the overlay2 copy-on-write driver, while network communication routes through private virtual bridge interfaces managed via iptables packet forwarding.
The Runtime Hierarchy: dockerd, containerd, and runc
In early releases, the Docker daemon was a monolithic binary that handled everything from user REST requests and building images to managing low-level process fork calls. Today, the container ecosystem follows Open Container Initiative (OCI) standards, decoupling responsibilities into distinct tiers:
dockerd (Docker Daemon): The user-facing management service. It processes incoming commands from the Docker CLI, authenticates image registries, coordinates Docker Compose definitions, and manages higher-level networking topologies.
containerd: An OCI-compliant core container supervisor. Originating inside Docker and now maintained by the Cloud Native Computing Foundation (CNCF), containerd manages image decompression, storage attachments, snapshotting, and container lifecycle monitoring. Kubernetes commonly connects straight to containerd via CRI without requiring dockerd.
containerd-shim: A tiny helper process spawned for every active container. The shim keeps standard input/output file descriptors open and reports exit codes back to containerd, allowing the parent daemon to restart or upgrade without crashing running containers.
runc: A lightweight command-line tool that interfaces directly with Linux kernel system calls. It creates isolated namespaces, configures cgroup resource boundaries, and calls execve to launch your application binary. Once the container process starts, runc exits completely.
Kernel Foundations: Namespaces and Control Groups (cgroups v2)
Containers exist because the Linux kernel provides two fundamental isolation primitives: Namespaces and Control Groups.
Linux Namespaces: What the Process Can See
Namespaces wrap global system resources into isolated virtual environments. When an application runs inside a container, its view of the machine is restricted to its assigned namespaces:
PID Namespace: Process ID virtualization. Inside the container, your web server sees itself as PID 1, while on the underlying host kernel, it runs as an ordinary process with PID 14920.
NET Namespace: Provides dedicated loopback adapters, private IP subnets, routing tables, and firewall filter rules isolated from the host physical network.
MNT Namespace: Mount point isolation. Gives the container its own private filesystem root (/), preventing access to the real host disk root directory.
UTS Namespace: Allows the container to declare its own hostname and domain name without impacting host identity.
IPC Namespace: Isolates shared memory segments and POSIX message queues.
USER Namespace: Maps a non-root user inside the container to an unprivileged UID on the host, preventing host root privilege escalation.
Control Groups (cgroups v2): What the Process Can Use
While namespaces isolate visibility, Control Groups enforce resource limits. Without cgroups, a single runaway thread could consume 100% of host RAM and trigger kernel panics. Cgroups enforce strict ceilings on:
Memory Caps: Hard limits like --memory="2g" ensure the host terminates offending processes via OOMKilled before host memory destabilizes.
CPU Bandwidth: Flags like --cpus="1.5" throttle CPU time slices using the Completely Fair Scheduler (CFS).
Block IO: Restricts read and write input/output operations per second (IOPS) to prevent storage disk saturation.
Container images share the host Linux kernel while running in isolated namespaces.
Container images are not monolithic disk clones. An image represents an ordered stack of immutable, read-only filesystem diffs. The overlay2 storage driver combines these distinct directories into a unified virtual directory tree using Linux union mounts.
The overlay filesystem organizes files across three key structural layers:
LowerDir (Read-Only): The stacked layers originating from your Dockerfile directives (e.g., FROM alpine, RUN apk add curl). Multiple containers instantiate from the same image simultaneously by sharing these identical read-only lower layers in host memory without duplicating storage.
UpperDir (Read-Write): When a container launches, Docker places a thin, mutable scratch layer on top. Any file created, edited, or deleted while the container runs is recorded exclusively inside this UpperDir.
MergedDir (Unified View): The consolidated mount point presented to the containerized application. The container views a standard directory structure where files in the UpperDir overlay matching filenames in the LowerDir.
This design relies on Copy-on-Write (CoW). If a container modifies a configuration file originating from a base image layer, the storage driver first copies the original file up into the writable UpperDir before writing modifications. The base image layer remains completely unchanged.
Persistent Storage: Named Volumes vs Host Bind Mounts
Because UpperDir writable container layers are ephemeral, removing a container completely destroys all files created inside it. For stateful software like relational databases or file uploads, infrastructure teams rely on dedicated persistence mechanisms:
Mount Mechanism
Host Location
Lifecycle and Ownership
Ideal Production Application
Named Volumes
/var/lib/docker/volumes/<name>/_data
Managed exclusively by Docker; persists indefinitely across container teardowns
Production databases (PostgreSQL, MySQL), message brokers, cache persistence
Host Bind Mounts
Arbitrary host paths (e.g., /opt/app/configs)
Direct access to host directory; relies on host filesystem permissions (UID/GID). Configuring Linux file permissions with chmod and chown on host directories prevents permission errors when unprivileged container processes write to mounted storage.
Local source code hot-reloading in development, mounting host SSL certificates
tmpfs Mounts
Host system RAM memory pool
Volatile; erased when the container stops; never written to physical disk
On production Linux nodes, named volumes achieve native storage performance because files write directly to ext4 or XFS host blocks, bypassing the copy-on-write CPU overhead of the overlay2 driver.
Container Networking: Bridge, Host, and Overlay Fabrics
Docker manages container communication through several pluggable network drivers:
1. Bridge Network (Default)
When Docker initializes, it creates a virtual Linux bridge adapter named docker0. When a container starts, Docker creates a virtual ethernet pair (veth). One end attaches to the container network namespace as eth0, and the opposite end plugs into docker0. The container receives an IP on a private subnet (such as 172.17.0.2). Outbound communication is translated through host iptables masquerade rules, while inbound communication uses port forwarding (e.g., -p 8080:80).
2. User-Defined Custom Bridges
The default bridge lacks embedded DNS service. Always create a custom bridge for microservices:
# Create custom bridge network with built-in DNS
docker network create internal-app-net
# Attach containers with automatic name resolution
docker run -d --name db-server --network internal-app-net postgres:16
docker run -d --name web-api --network internal-app-net -p 3000:3000 my-api
Inside web-api, your application can reach the database using the hostname db-server rather than fragile hardcoded IP addresses.
3. Host and Overlay Modes
Using --network host disables network namespace virtualization. The container binds directly to host physical interfaces, eliminating NAT translation latency at the expense of port collision risk. In multi-host clusters (like Docker Swarm), the Overlay driver creates an encrypted VXLAN mesh across multiple physical servers, routing container packets directly between distinct cloud instances.
Named Docker volumes decouple stateful database storage from container lifecycles.
Container Lifecycle States and Healthcheck Monitoring
A container transitions through well-defined operational phases: Created, Running, Paused, Restarting, and Exited.
By default, Docker only checks whether PID 1 is actively running. If your web application deadlocks internally or throws database connection loops while the Node.js process stays alive, Docker considers the container healthy. Adding an explicit HEALTHCHECK instruction inside your Dockerfile allows the engine to detect internal application stalls:
An exit code of 137 indicates the container received signal 9 (SIGKILL). If OOMKilled displays true, the process exceeded its cgroup memory quota. Increase memory allocation or optimize application garbage collection parameters. When hosting containers in cloud datacenters, evaluating AWS EC2 instance types ensures your virtual machines provide adequate RAM head-room and burstable bandwidth for container clusters.
2. Fixing Port Allocation Conflicts
If launching a container errors with "bind: address already in use", identify which host service occupies the port using sudo ss -tulpn | grep :8080. Either stop the host daemon or bind the container to an alternative host port such as -p 8081:80.
Frequently Asked Questions
Named volumes are managed by Docker inside its storage directory (/var/lib/docker/volumes) with standardized storage drivers and permission handling. Bind mounts attach an arbitrary directory from the host filesystem directly to the container.
Evan Mitchell• Cloud Infrastructure Specialist & Systems Administrator3+ Years Industry Experience
Systems administrator with 3+ years managing enterprise Linux servers, AWS EC2 instances, and Docker containers. Evan focuses on practical bash scripting and secure network configurations.