The Docker Ecosystem Overview
Before Docker, using Linux containers required manual orchestration of namespaces, cgroups, and chroot — an approach accessible only to kernel engineers. Docker's genius was packaging these primitives into a simple CLI workflow: docker build, docker push, docker run. But the Docker you use today is a composite of many components, some controlled by Docker Inc. and others donated to open-source foundations.
Open Source vs Commercial
| Component | Owner | License | Purpose |
|---|---|---|---|
| Docker CLI | Docker Inc. (open source) | Apache 2.0 | Client-side command interface |
| Docker Engine (dockerd) | Docker Inc. (open source) | Apache 2.0 | API server, image/volume/network management |
| containerd | CNCF (graduated project) | Apache 2.0 | Container runtime — lifecycle, image, snapshot management |
| runc | Open Containers Initiative (OCI) | Apache 2.0 | Low-level OCI runtime — creates containers using kernel APIs |
| Docker Compose | Docker Inc. (open source) | Apache 2.0 | Multi-container application definition and orchestration |
| Docker Desktop | Docker Inc. | Proprietary (free for personal/small biz) | GUI app for macOS/Windows with embedded Linux VM |
| Docker Hub | Docker Inc. | Proprietary (SaaS) | Public/private container image registry |
| BuildKit | Docker Inc. (open source) | Apache 2.0 | Next-generation image builder with parallelism and caching |
flowchart TB
subgraph user["User Layer"]
CLI["Docker CLI"]
Compose["Docker Compose"]
Desktop["Docker Desktop
(macOS/Windows)"]
end
subgraph engine["Docker Engine"]
Daemon["dockerd
(API Server)"]
BuildKit["BuildKit
(Image Builder)"]
end
subgraph runtime["Container Runtime"]
CTD["containerd
(CNCF Graduated)"]
Shim["containerd-shim"]
RunC["runc
(OCI Runtime)"]
end
subgraph kernel["Linux Kernel"]
NS["Namespaces"]
CG["cgroups"]
OFS["OverlayFS"]
SEC["seccomp / AppArmor"]
end
subgraph registry["Registry"]
Hub["Docker Hub"]
Private["Private Registry
(Harbor, ECR, ACR)"]
end
CLI --> Daemon
Compose --> Daemon
Desktop --> Daemon
Daemon --> CTD
Daemon --> BuildKit
CTD --> Shim --> RunC
RunC --> NS & CG & OFS & SEC
Daemon <--> Hub
Daemon <--> Private
docker) that did everything. Starting in 2016, Docker Inc. began disaggregating components: first donating containerd to the CNCF, then runc to the OCI. This separation of concerns means Kubernetes can use containerd directly (without Docker), and alternative tools like Podman can use the same runc binary. Docker's value today is primarily in developer experience (CLI, Compose, Desktop) rather than the runtime itself.
Docker Engine Architecture
The Docker Engine follows a client-server architecture with three primary tiers, each communicating through well-defined APIs:
Communication Protocols
| From | To | Protocol | Default Socket |
|---|---|---|---|
| Docker CLI | dockerd | HTTP REST API (over Unix socket or TCP) | /var/run/docker.sock |
| dockerd | containerd | gRPC (Protocol Buffers) | /run/containerd/containerd.sock |
| containerd | runc | Exec binary (fork/exec with OCI bundle path) | N/A (binary invocation) |
| containerd | shim | ttrpc (lightweight gRPC variant) | Abstract Unix socket per container |
sequenceDiagram
participant User
participant CLI as Docker CLI
participant API as dockerd (REST API)
participant CTD as containerd (gRPC)
participant Shim as containerd-shim
participant RunC as runc
participant Kernel as Linux Kernel
User->>CLI: docker run nginx
CLI->>API: POST /containers/create
API->>API: Resolve image, prepare config
API->>CTD: Create container (gRPC)
CTD->>CTD: Prepare snapshot (OverlayFS layers)
CTD->>Shim: Start shim process
Shim->>RunC: Fork+exec runc create
RunC->>Kernel: clone() with namespaces
RunC->>Kernel: Write cgroup limits
RunC->>Kernel: Mount OverlayFS
RunC->>Kernel: pivot_root()
RunC-->>Shim: Container PID
Note over RunC: runc exits after creation
Shim-->>CTD: Container started
CTD-->>API: Container ID
API-->>CLI: Container ID
CLI-->>User: abc123def456
Docker CLI
The Docker CLI (docker) is a Go binary that translates user commands into HTTP REST API calls to the Docker daemon. It's a pure client — it never creates containers itself. This separation means you can control a remote Docker daemon from your local machine, or even use alternative clients that speak the same API.
# The CLI communicates with the daemon via a Unix socket by default
ls -la /var/run/docker.sock
# srw-rw---- 1 root docker 0 May 14 08:00 /var/run/docker.sock
# You can manually call the API the same way the CLI does
curl --unix-socket /var/run/docker.sock http://localhost/version | jq .
# {
# "Platform": { "Name": "Docker Engine - Community" },
# "Version": "27.0.3",
# "ApiVersion": "1.46",
# "Os": "linux",
# "Arch": "amd64",
# "KernelVersion": "6.5.0-35-generic",
# "GoVersion": "go1.22.3"
# }
# List containers via raw API call (same as `docker ps`)
curl --unix-socket /var/run/docker.sock http://localhost/containers/json | jq .[].Names
# ["/my-nginx"]
# ["/redis-cache"]
# Connect to a REMOTE Docker daemon (e.g., on a build server)
export DOCKER_HOST=tcp://build-server.internal:2376
export DOCKER_TLS_VERIFY=1
export DOCKER_CERT_PATH=~/.docker/certs
# Now all docker commands target the remote machine
docker ps # Shows containers on build-server, not localhost
# SSH-based remote access (more secure, no daemon TCP exposure)
export DOCKER_HOST=ssh://user@build-server.internal
docker ps # Tunnels through SSH, no TLS config needed
# Docker contexts: save multiple remote configurations
docker context create production --docker "host=ssh://deploy@prod.example.com"
docker context create staging --docker "host=ssh://deploy@staging.example.com"
docker context use production
docker ps # Now targeting production server
/var/run/docker.sock) grants root-equivalent access to the host. Any process that can communicate with the socket can mount the host filesystem, access all networks, and escalate to root. Never mount the Docker socket into untrusted containers. In production, use TLS mutual authentication for remote daemon access, or SSH tunneling as a more secure alternative.
Docker Daemon (dockerd)
The Docker daemon (dockerd) is a long-running background process that serves the Docker API and orchestrates all Docker operations. It manages:
- Image management — Pulling, building, tagging, pushing, layer caching
- Container lifecycle — Delegated to containerd, but dockerd maintains state and metadata
- Volume management — Creating, mounting, and garbage-collecting volumes
- Network management — Creating bridges, managing iptables rules, DNS resolution
- Plugin management — Volume drivers, network drivers, authorization plugins
- Build operations — Coordinating BuildKit for image builds
# View the daemon process
ps aux | grep dockerd
# root 1234 0.1 0.5 1923456 45678 ? Ssl 08:00 0:03 /usr/bin/dockerd
# -H fd:// --containerd=/run/containerd/containerd.sock
# Check daemon status via systemd
systemctl status docker
# ● docker.service - Docker Application Container Engine
# Loaded: loaded (/lib/systemd/system/docker.service; enabled)
# Active: active (running) since Wed 2026-05-14 08:00:00 UTC
# Main PID: 1234 (dockerd)
# View daemon logs
journalctl -u docker --since "1 hour ago" --no-pager | tail -20
// /etc/docker/daemon.json — Daemon configuration
{
"storage-driver": "overlay2",
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
},
"default-address-pools": [
{ "base": "172.20.0.0/16", "size": 24 }
],
"dns": ["8.8.8.8", "8.8.4.4"],
"live-restore": true,
"userland-proxy": false,
"experimental": false,
"metrics-addr": "127.0.0.1:9323",
"insecure-registries": [],
"registry-mirrors": ["https://mirror.gcr.io"]
}
"live-restore": true setting is crucial for production. It allows running containers to continue operating even when the Docker daemon is stopped or restarted (for upgrades). Without it, stopping dockerd would kill all containers — disastrous in production. The containers are kept alive by containerd-shim processes that survive daemon restarts.
containerd
containerd (pronounced "container-dee") is an industry-standard container runtime that manages the complete container lifecycle on a host. Originally built as a Docker component, it was donated to the Cloud Native Computing Foundation (CNCF) in 2017 and graduated in 2019 — a testament to its maturity and wide adoption.
containerd handles:
- Image pull/push — Fetching layers from registries, content-addressable storage
- Snapshot management — Preparing OverlayFS mounts for containers
- Container lifecycle — Create, start, stop, delete containers via shims
- Task management — Tracking running processes inside containers
- Content store — Deduplicating and storing image layer blobs
- Events — Publishing container lifecycle events for monitoring
# containerd runs as its own systemd service
systemctl status containerd
# ● containerd.service - containerd container runtime
# Active: active (running)
# Use ctr (containerd's native CLI) to interact directly
# List containers managed by containerd
sudo ctr --namespace moby containers list
# CONTAINER IMAGE RUNTIME
# abc123def456 docker.io/library/nginx:latest io.containerd.runc.v2
# List images in containerd's store
sudo ctr --namespace moby images list | head -5
# REF TYPE DIGEST SIZE
# docker.io/library/nginx:latest application/vnd.oci... sha256:3b2... 67.3 MiB
# View containerd's namespaces (Docker uses "moby" namespace)
sudo ctr namespaces list
# NAME LABELS
# moby
# k8s.io (if Kubernetes is also using containerd)
containerd: From Docker Component to Industry Standard
containerd's independence from Docker was a pivotal moment in container history. Key milestones:
- 2016 — Docker extracts containerd as a separate daemon
- 2017 — Donated to CNCF; Kubernetes adds containerd as a CRI runtime option
- 2019 — CNCF graduation (same status as Kubernetes itself)
- 2022 — Kubernetes removes dockershim; containerd becomes the primary runtime
- 2024+ — Used by AWS Fargate, Google GKE, Azure AKS, most cloud container services
This means you can run containers without Docker installed at all — just containerd + runc. Kubernetes clusters typically have no Docker daemon; they communicate with containerd directly via the Container Runtime Interface (CRI).
runc
runc is the lowest-level component — a lightweight CLI tool that creates and runs containers according to the OCI (Open Container Initiative) Runtime Specification. It's the component that actually calls clone() with namespace flags, writes cgroup configuration, mounts OverlayFS, and executes pivot_root() to enter the container.
# runc is a standalone binary
which runc
# /usr/bin/runc
runc --version
# runc version 1.1.12
# commit: v1.1.12-0-g51d5e946
# spec: 1.0.2-dev
# go: go1.22.3
# libseccomp: 2.5.4
# runc operates on OCI bundles — a directory with:
# config.json (OCI runtime spec — namespaces, mounts, cgroups)
# rootfs/ (the container's root filesystem)
# View what runc does (educational — normally containerd handles this)
sudo runc spec # Generate a default OCI config.json
cat config.json | jq '.linux.namespaces'
# [
# { "type": "pid" },
# { "type": "network" },
# { "type": "ipc" },
# { "type": "uts" },
# { "type": "mount" },
# { "type": "cgroup" }
# ]
containerd-shim, which monitors it for the rest of its lifecycle. This means runc can be upgraded without affecting running containers — a critical property for production systems.
Core Concept: Images
A Docker image is an immutable, ordered collection of filesystem layers plus metadata (environment variables, default command, exposed ports, labels). Images are identified by two mechanisms:
- Tags — Human-readable names like
nginx:1.25ormyapp:latest(mutable — can be reassigned) - Digests — Content-addressable SHA256 hashes like
sha256:3b25b682ea...(immutable — content defines identity)
# Pull an image (downloads layers not already cached)
docker pull nginx:1.25
# 1.25: Pulling from library/nginx
# bd159e379b3b: Already exists ← cached base layer
# 6d5b3ea3b509: Pull complete ← new layer downloaded
# Digest: sha256:3b25b682ea82b2db3...
# Status: Downloaded newer image for nginx:1.25
# List local images
docker images
# REPOSITORY TAG IMAGE ID CREATED SIZE
# nginx 1.25 3b25b682ea82 2 weeks ago 187MB
# nginx latest 3b25b682ea82 2 weeks ago 187MB ← same image!
# node 18-slim a1b2c3d4e5f6 3 weeks ago 243MB
# Inspect image metadata
docker image inspect nginx:1.25 --format '{{json .Config}}' | jq .
# {
# "Hostname": "",
# "Env": ["PATH=/usr/local/sbin:...", "NGINX_VERSION=1.25.5"],
# "Cmd": ["nginx", "-g", "daemon off;"],
# "ExposedPorts": { "80/tcp": {} },
# "Labels": { "maintainer": "NGINX Docker Maintainers" }
# }
# Pin to a specific digest for reproducible deployments
docker pull nginx@sha256:3b25b682ea82b2db3cc4...
# This will ALWAYS pull the exact same image, regardless of tag changes
Core Concept: Containers
A container is a running (or stopped) instance of an image. It adds a writable layer on top of the image's read-only layers and maintains runtime state (PID, network config, environment variables, mounted volumes). Containers have a defined lifecycle with distinct states:
| State | Description | Transition Commands |
|---|---|---|
| Created | Container configured but process not started | docker create → Created |
| Running | Main process is active | docker start or docker run |
| Paused | Process frozen via cgroup freezer (SIGSTOP) | docker pause / docker unpause |
| Stopped | Main process exited (exit code preserved) | docker stop (SIGTERM→SIGKILL) or process exits |
| Deleted | Container metadata and writable layer removed | docker rm |
# Complete container lifecycle demonstration
# 1. CREATE — configure but don't start
docker create --name lifecycle-demo -p 8080:80 nginx:latest
# abc123def456... (container ID returned, state: Created)
# 2. START — begin running the container process
docker start lifecycle-demo
# lifecycle-demo (state: Running)
# 3. PAUSE — freeze the process (zero CPU, memory preserved)
docker pause lifecycle-demo
docker inspect lifecycle-demo --format '{{.State.Status}}'
# paused
# 4. UNPAUSE — resume execution
docker unpause lifecycle-demo
# 5. STOP — graceful shutdown (SIGTERM, then SIGKILL after 10s)
docker stop lifecycle-demo
docker inspect lifecycle-demo --format '{{.State.ExitCode}}'
# 0 (clean shutdown)
# 6. RESTART — stop + start in one command
docker restart lifecycle-demo
# 7. REMOVE — delete container and its writable layer
docker stop lifecycle-demo
docker rm lifecycle-demo
# One-shot: run + auto-remove on exit
docker run --rm --name temp nginx:latest echo "hello"
# hello (container automatically deleted after echo exits)
Core Concept: Registries
A container registry is a server that stores and distributes container images. Registries implement the OCI Distribution Specification (originally the Docker Registry HTTP API V2), providing endpoints for pushing, pulling, and discovering images.
| Registry | Provider | Use Case | Free Tier |
|---|---|---|---|
| Docker Hub | Docker Inc. | Public images, official images, personal projects | Unlimited public repos, 1 private repo |
| GitHub Container Registry (ghcr.io) | GitHub/Microsoft | Open source projects, GitHub Actions CI/CD | Unlimited public, generous private |
| Amazon ECR | AWS | Production workloads on AWS (ECS, EKS, Lambda) | 500 MB storage free tier |
| Azure Container Registry | Microsoft | Production workloads on Azure (AKS, Container Apps) | Basic tier included in subscription |
| Google Artifact Registry | Google Cloud | Production workloads on GCP (GKE, Cloud Run) | 500 MB storage free tier |
| Harbor | CNCF (open source) | Self-hosted enterprise registry with scanning, RBAC | Self-hosted (free software) |
| Quay.io | Red Hat | Red Hat ecosystem, OpenShift integration | Unlimited public repos |
# Tag and push an image to a registry
docker tag myapp:latest ghcr.io/username/myapp:v1.2.3
docker push ghcr.io/username/myapp:v1.2.3
# The push refers to repository [ghcr.io/username/myapp]
# 5f70bf18a086: Pushed ← only unique layers are uploaded
# 3b25b682ea82: Mounted ← shared layer already exists in registry
# v1.2.3: digest: sha256:abc123... size: 1234
# Login to a private registry
docker login ghcr.io -u username
# Password: (enter personal access token)
# Login Succeeded
# Pull from a specific registry
docker pull ghcr.io/username/myapp:v1.2.3
# Run a local registry for development/testing
docker run -d -p 5000:5000 --name registry registry:2
docker tag myapp:latest localhost:5000/myapp:latest
docker push localhost:5000/myapp:latest
Core Concept: Volumes
Volumes solve the ephemeral container layer problem — they provide persistent storage that survives container deletion. Docker supports three storage mechanisms:
| Type | Syntax | Managed By | Use Case |
|---|---|---|---|
| Named Volume | -v mydata:/app/data |
Docker (stored in /var/lib/docker/volumes/) |
Database storage, persistent application data |
| Bind Mount | -v /host/path:/container/path |
User (host filesystem) | Development (live code reload), sharing host files |
| tmpfs Mount | --tmpfs /tmp |
Kernel (RAM-backed) | Sensitive data that shouldn't persist, fast scratch space |
# Named volumes: Docker manages the storage location
docker volume create app-data
docker run -d --name db -v app-data:/var/lib/postgresql/data postgres:16
# Data persists even after: docker rm db
# Inspect a volume
docker volume inspect app-data
# [{ "Name": "app-data",
# "Driver": "local",
# "Mountpoint": "/var/lib/docker/volumes/app-data/_data",
# "CreatedAt": "2026-05-14T10:00:00Z" }]
# Bind mounts: map host directory into container
docker run -d --name dev-app \
-v $(pwd)/src:/app/src \
-v $(pwd)/config.yml:/app/config.yml:ro \
node:18 npm run dev
# :ro = read-only inside container (can't accidentally modify host file)
# tmpfs: RAM-backed temporary storage (never written to disk)
docker run -d --name secure-app \
--tmpfs /tmp:size=100m \
--tmpfs /run/secrets:size=1m,mode=0700 \
myapp:latest
docker-compose down && docker-compose up) and can be backed up independently with docker run --rm -v app-data:/data -v /backup:/backup alpine tar czf /backup/snapshot.tar.gz /data.
Core Concept: Networks
Docker networking provides containers with isolated network stacks (from network namespaces) while allowing controlled communication between containers and the outside world. Docker ships with several network drivers:
| Driver | Scope | DNS Discovery | Use Case |
|---|---|---|---|
| bridge | Single host | Yes (user-defined bridges) | Default for standalone containers; isolated communication |
| host | Single host | N/A (uses host network) | Maximum network performance; no isolation |
| overlay | Multi-host (Swarm) | Yes | Cross-host container communication in clusters |
| macvlan | Single host | No | Container gets its own MAC address on physical network |
| none | N/A | No | Complete network isolation (security-sensitive workloads) |
# Create a user-defined bridge network
docker network create --driver bridge --subnet 172.25.0.0/16 app-network
# Run containers on the same network (they can reach each other by name)
docker run -d --name api --network app-network myapi:latest
docker run -d --name db --network app-network postgres:16
# The 'api' container can reach the database via DNS name 'db'
docker exec api ping db
# PING db (172.25.0.3): 56 data bytes
# 64 bytes from 172.25.0.3: seq=0 ttl=64 time=0.067 ms
# Inspect network details
docker network inspect app-network --format '{{json .Containers}}' | jq .
# {
# "abc123": { "Name": "api", "IPv4Address": "172.25.0.2/16" },
# "def456": { "Name": "db", "IPv4Address": "172.25.0.3/16" }
# }
# List all networks
docker network ls
# NETWORK ID NAME DRIVER SCOPE
# a1b2c3d4e5f6 bridge bridge local ← default (no DNS)
# f6e5d4c3b2a1 host host local
# 1a2b3c4d5e6f none null local
# 9f8e7d6c5b4a app-network bridge local ← user-defined (DNS works)
Default Bridge vs User-Defined Bridge
The default bridge network (docker0) has a critical limitation: containers can only communicate by IP address, not by name. User-defined bridges provide automatic DNS resolution — containers can reach each other by container name. This is why Docker Compose always creates a dedicated network for its services.
- Default bridge:
docker run nginx→ no DNS, manual--link(deprecated), all containers share one bridge - User-defined bridge:
docker network create mynet→ automatic DNS, network isolation, better iptables rules
Always create user-defined networks for multi-container applications. The default bridge exists only for backwards compatibility.
Putting It All Together
Now that we understand each component, let's trace the complete journey of docker run -d -p 8080:80 --name web nginx:latest — from keypress to running container:
sequenceDiagram
participant U as User Terminal
participant CLI as docker CLI
participant D as dockerd
participant CTD as containerd
participant S as containerd-shim
participant R as runc
participant K as Linux Kernel
participant Reg as Docker Hub
U->>CLI: docker run -d -p 8080:80 nginx
CLI->>D: POST /images/create?fromImage=nginx&tag=latest
D->>Reg: GET /v2/library/nginx/manifests/latest
Reg-->>D: Image manifest (layer digests)
D->>Reg: GET /v2/library/nginx/blobs/sha256:...
Reg-->>D: Layer data (parallel downloads)
D-->>CLI: Image pulled successfully
CLI->>D: POST /containers/create {Image: nginx, HostConfig: {PortBindings: 8080→80}}
D->>D: Allocate container ID, validate config
D->>CTD: CreateContainer (gRPC)
CTD->>CTD: Prepare OverlayFS snapshot (stack image layers)
D-->>CLI: {Id: "abc123..."}
CLI->>D: POST /containers/abc123/start
D->>CTD: StartContainer (gRPC)
CTD->>S: Fork containerd-shim-runc-v2
S->>R: exec runc create --bundle /run/containerd/...
R->>K: clone(CLONE_NEWPID | CLONE_NEWNET | CLONE_NEWNS | ...)
R->>K: Write /sys/fs/cgroup/.../memory.max, cpu.max
R->>K: mount("overlay", "/merged", ...)
R->>K: pivot_root("/merged", "/merged/.pivot")
R->>K: execve("/docker-entrypoint.sh")
Note over R: runc exits (process reparented to shim)
S-->>CTD: Container PID = 4567
D->>K: iptables -t nat -A DOCKER -p tcp --dport 8080 -j DNAT --to 172.17.0.2:80
CTD-->>D: Started
D-->>CLI: 204 No Content
CLI-->>U: abc123def456 (container running!)
The entire process from command to running container takes roughly 1-3 seconds (depending on whether the image is cached). Here's what each phase accomplished:
- Image resolution — CLI asks daemon, daemon checks local cache, pulls missing layers from registry
- Container creation — Daemon allocates ID and metadata, containerd prepares the OverlayFS mount
- Container start — Shim forks runc, runc creates namespaces and cgroups, starts the process, then exits
- Networking — Daemon configures iptables port forwarding from host:8080 to container:80
- Monitoring — Shim keeps the container running and reports status back to containerd/daemon
Exercises
- API Exploration — Use
curlwith the Docker socket to list containers, inspect an image, and create+start a container entirely through raw API calls (nodockerCLI). Document each endpoint you call and the response structure. - containerd Direct Access — Use
ctr(containerd's CLI) in themobynamespace to list containers and images. Pull an image directly withctr images pull(bypassing Docker) and compare withdocker images. - Network Lab — Create three user-defined networks (
frontend,backend,database). Run an nginx container onfrontend, an API container on bothfrontendandbackend, and a PostgreSQL container ondatabaseandbackend. Verify that nginx cannot directly reach PostgreSQL but the API can reach both. - Volume Persistence Test — Create a PostgreSQL container with a named volume. Insert data. Remove and recreate the container with the same volume. Verify data survives. Then repeat without a volume and confirm data loss.
- Architecture Trace — Run
docker run -d nginxand then useps aux,ls /proc/<PID>/ns/,cat /sys/fs/cgroup/.../, andmount | grep overlayto find all the kernel resources (namespaces, cgroups, OverlayFS mount) that were created. Map each resource back to the architectural component that created it.
Conclusion & Next Steps
Docker's architecture is an elegant layering of responsibilities: the CLI provides developer experience, the daemon manages state and coordination, containerd handles runtime lifecycle, and runc interfaces with the kernel. Understanding this separation helps you:
- Debug effectively — Know which component to investigate when things go wrong
- Understand Kubernetes — K8s uses containerd directly, bypassing Docker entirely
- Make architectural decisions — Choose between Docker, Podman, containerd, or other tools
- Secure your infrastructure — Understand the trust boundaries between components
Key takeaways:
- Docker CLI is a stateless client that communicates via REST API over a Unix socket
- dockerd is the API server managing images, volumes, networks, and build operations
- containerd is a CNCF-graduated runtime handling container lifecycle and image management
- runc creates containers using kernel namespaces and cgroups, then exits
- Images are immutable layer stacks; containers add a writable layer on top
- Volumes provide persistence beyond container lifecycle; networks provide isolation and discovery
- The containerd-shim decouples running containers from daemon lifecycle
With the architecture understood, Part 6 will put this knowledge into practice with a comprehensive exploration of the Docker CLI — the primary interface for building, running, inspecting, and debugging containers.
Next in the Series
In Part 6: Docker CLI Mastery, we will explore every essential Docker command — from container lifecycle and image management to inspection, debugging, and system maintenance — building fluency with the tool you'll use daily.