Chapter 01Lesson 01~95 minutes

Containers, Virtual Machines, Linux Isolation, OCI Standards, and Docker Architecture: Concepts, Architecture, and Mental Model

Build the correct Docker mental model before memorizing commands: distinguish containers from virtual machines, understand Linux namespace/cgroup isolation, place OCI standards at the right boundaries, and trace Docker client intent through the Engine and runtime stack to observable process state.

FoundationsContainers vs VMsLinux isolationOCIDocker architecture

Learning objectives

  • Explain why a Linux container is an isolated process environment that shares the daemon host kernel rather than a miniature virtual machine.
  • Distinguish namespaces, cgroups, capabilities, seccomp/LSM policy, filesystem mounts, and runtime configuration as separate isolation and security mechanisms.
  • Map Docker CLI/context → Engine API → dockerd → containerd/runtime → container process without treating implementation details as an immutable product contract.
  • Place the OCI Image, Runtime, and Distribution specifications at the correct interoperability boundaries.
  • Inspect host/platform, Docker context, client/server/API versions, image digest, container ID/PID, namespaces, cgroups, networks, and mounts as separate evidence.
Chapter 01 technical baseline — verified 2026-09-20. Docker Engine 29.8.1 is the current Engine 29 patch release in Docker's release notes. Its packages update the bundled containerd static binaries to v2.3.5. Docker and OCI internals are version-sensitive, so every lab starts by recording the actual client, server, API, kernel, context, cgroup, runtime, and image identity instead of assuming this baseline.

1. Start with the real problem: what exactly is a container?

A beginner can run docker run in seconds and still carry an incorrect mental model for months. The most common mistake is to imagine a container as a tiny virtual machine. That metaphor is convenient for the first minute, but it fails as soon as you debug a kernel feature, a host mount, a process signal, a capability denial, a cgroup limit, or a Docker Desktop environment.

A Linux container is better understood as a host-kernel process tree observed through isolation and resource-control mechanisms, plus filesystem and runtime metadata assembled by a container engine. The process can see a different process-ID namespace, mount tree, hostname, network stack, IPC objects, and user-ID mapping from the host. Cgroups can account for and constrain resources. Security mechanisms such as capabilities, seccomp, AppArmor, or SELinux narrow what the process may do. None of those mechanisms gives the process its own Linux kernel.

Do not infer a separate kernel from a separate filesystem. Seeing /etc/os-release report Alpine, Debian, or Ubuntu describes userspace files in the image. On native Linux Engine, the container still issues system calls to the host kernel. On Docker Desktop, Linux containers use the kernel of Docker Desktop's Linux VM rather than the physical macOS/Windows host.

2. Containers and virtual machines isolate at different layers

A virtual machine normally virtualizes hardware and boots a guest operating system with its own kernel. A Linux container normally shares a kernel with the Docker daemon's Linux host while isolating selected process views and applying policy. Both can be useful. The important engineering question is not “which one is better?” but “which boundary matches the workload and threat model?”

Dimension Linux container Virtual machine Operational consequence
Kernel Shares the daemon host's Linux kernel Boots a guest kernel A container cannot choose an unrelated kernel version; a VM can.
Isolation primitive Namespaces, cgroups, capabilities, seccomp/LSM, filesystem/runtime configuration Hypervisor/virtual hardware plus guest OS controls Threat boundaries are different; neither label alone proves sufficient isolation.
Startup/footprint Usually process-oriented and relatively lightweight Includes guest OS boot and memory footprint Density and startup goals may favor containers, while stronger kernel separation may favor VMs.
Artifact OCI/Docker image plus runtime configuration VM disk image/template plus virtual hardware configuration Promotion and patching workflows differ.
Kernel attack surface Containerized processes ultimately call a shared kernel Guest kernel is separated by the virtualization boundary Hostile multi-tenancy often needs stronger isolation than ordinary containers alone.

Docker Desktop demonstrates that the two models can be layered: Desktop runs Linux containers inside a managed Linux VM. The container is still a container relative to that VM's kernel, while the VM provides another boundary relative to the physical host.

3. Linux isolation is several mechanisms, not one switch

Namespaces answer “what can this process see as its world?” Cgroups answer “how is this process accounted for or constrained?” Capabilities split traditional root power into narrower privileges. Seccomp filters system calls. AppArmor and SELinux can add mandatory access-control policy. Filesystem mounts decide what host or volume data becomes visible. Docker composes these mechanisms; none should be treated as a magic wall.

PID namespace

Changes the process-ID view. PID 1 inside the container is not normally host PID 1.

Mount namespace

Gives the process a distinct mount view assembled from image layers and explicit mounts.

Network namespace

Provides interfaces, routes, sockets, and firewall/NAT relationships distinct from the host namespace.

UTS namespace

Allows a container-specific hostname/domain-name view.

Cgroups

Account for and optionally limit CPU, memory, PIDs, and other resources. Current Docker documentation recommends cgroup v2 migration.

Security layers

Capabilities, the default seccomp profile, and host LSM policy reduce privilege beyond namespace separation.

When a command fails with “operation not permitted,” the right question is therefore not “how do I disable security?” It is “which layer denied which operation, and should this workload have that privilege?” Chapter 25 returns to this security model in depth.

4. OCI standards define portable contracts, not the whole Docker product

The Open Container Initiative (OCI) publishes three major specifications: the Image Specification, Runtime Specification, and Distribution Specification. They create interoperability boundaries so that an image layout, a runtime bundle, and registry distribution behavior are not proprietary to one engine.

OCI contract Question it answers Docker relationship
Image Specification How are image manifests/configuration/layers represented? Docker can build, store, pull, and run OCI-compatible image content.
Runtime Specification How is a filesystem bundle plus config.json executed and managed? Low-level runtimes such as runc implement this boundary.
Distribution Specification How can content be pushed to and pulled from registries? Registry workflows use compatible content-addressed distribution APIs.

OCI does not define Docker Compose, Docker Desktop, Docker contexts, Docker's entire Engine API, or every host integration detail. Standards define important seams; the Docker product adds orchestration, build, UX, networking, storage, security defaults, and platform integration around them.

5. Follow the control path from intent to a running process

For the common Linux Engine model, your command starts at the Docker client. The client selects a context and talks to the Engine API endpoint. dockerd owns Docker's higher-level objects and policy. Runtime work is delegated through containerd services, and a low-level OCI runtime such as runc creates the container process according to runtime configuration. After creation, a shim helps decouple container process lifecycle from the higher-level client session.

Control and evidence path — ownership is the point, not memorizing process names
flowchart TD
  A[Developer or automation intent] --> B[Docker CLI or SDK]
  B --> C[Selected context and Engine API]
  C --> D[dockerd: Docker objects and policy]
  D --> E[containerd-managed runtime services]
  E --> F[shim and OCI runtime such as runc]
  F --> G[Namespaced and cgroup-governed Linux process]
  G --> H[Logs, filesystem, network, health, exit evidence]
  D --> I[Image, network, volume and container metadata]
            

Do not turn this diagram into a frozen implementation promise. Docker Engine is evolving its containerd integration, including an experimental embedded-containerd mode documented in 2026. For operations and incident response, inspect the actual Engine version and process/runtime state rather than assuming every installation has identical internal process topology.

6. Separate object identity from runtime state

Docker makes several related objects easy to manipulate through one CLI, which can hide important boundaries. An image is immutable content identified by digests. A container is a configured runtime object created from image content plus runtime settings. The container's main process can be running or exited. A network attachment and mount can exist independently of whether the application inside is healthy.

State Useful evidence What success does not prove
Client can reach daemon docker version, context endpoint That any image or container is healthy.
Image present locally image ID/config digest/RepoDigest That a container has been created or started.
Container object created container ID and inspect configuration That its process is running.
Main process running .State.Running, PID, process list That an application-level health condition is good.
Network attached network ID, endpoint/IP, routes That a port is published or reachable.
Mount attached mount type/source/destination That data is backed up, consistent, or durable after deletion.

7. Inspect the environment before you create anything

The following commands are read-only. They establish which client is running, which daemon it will contact, which kernel/runtime environment the daemon reports, and whether cgroup v2 is in use. Save the output before troubleshooting or changing configuration.

date -u +%Y-%m-%dT%H:%M:%SZ
uname -a

docker context show
docker context inspect "$(docker context show)"
docker version
docker info

On a native Linux Engine host, uname -a describes the same kernel used by Linux containers. On Docker Desktop for macOS/Windows—and Docker Desktop for Linux as documented today—the daemon is inside a managed Linux VM, so the physical-host kernel and container kernel are not the same observation. Record that platform boundary instead of forcing Linux-host commands onto Desktop.

API evidence, not API folklore. The current Docker API documentation is internally inconsistent about the exact maximum API number shown for Engine 29.8.x in its example versus version matrix. This lesson intentionally asks you to record the client/server/API values printed by docker version. That is the evidence for your actual pair.

8. Resolve a tag, then run the resolved digest

A tag such as alpine:3.22 is a human-friendly mutable reference. For a reproducible lab, first pull the tag, capture the registry digest Docker reports locally, and then use that digest reference for the actual container. This teaches a useful habit without freezing a digest into course prose that may become obsolete.

LAB_IMAGE_TAG="alpine:3.22"
LAB_CONTAINER="da-ch01-model"

docker pull "$LAB_IMAGE_TAG"
ALPINE_REF="$(docker image inspect "$LAB_IMAGE_TAG" --format '{{index .RepoDigests 0}}')"
printf 'Resolved image: %s
' "$ALPINE_REF"

docker run -d \
  --name "$LAB_CONTAINER" \
  --label devops-academy.lab=chapter01 \
  "$ALPINE_REF" \
  sh -c 'echo "chapter01 container started"; exec sleep 900'

docker inspect "$LAB_CONTAINER" --format \
  'id={{.Id}} image={{.Image}} pid={{.State.Pid}} running={{.State.Running}}'
docker logs "$LAB_CONTAINER"

The RepoDigest proves what registry content the tag resolved to at pull time. The container's .Image field identifies the local image configuration object used by the container. Those identifiers are related but not interchangeable; later chapters unpack image/index/manifest/config identity precisely.

9. Prove the shared-kernel/process model

Inside the container, inspect the kernel release and process view. Then compare with the daemon host. Native Linux users can also map Docker's container PID to the host process directly.

docker exec "$LAB_CONTAINER" uname -a
docker exec "$LAB_CONTAINER" ps -o pid,ppid,user,comm

docker inspect "$LAB_CONTAINER" --format 'host-pid={{.State.Pid}}'

# Native Linux Engine host only:
PID="$(docker inspect "$LAB_CONTAINER" --format '{{.State.Pid}}')"
ps -fp "$PID"
readlink "/proc/$PID/ns/pid"
readlink "/proc/$PID/ns/mnt"
readlink "/proc/$PID/ns/net"
cat "/proc/$PID/cgroup"

Inside the PID namespace, the container's main process normally appears as PID 1. The same process has a different PID in the host's PID namespace. That is isolation of the process-ID view, not a second kernel.

10. Treat Docker Desktop's VM as an explicit architecture boundary

Docker Desktop for Linux documents that it runs a VM and uses a dedicated desktop-linux context. Docker Desktop on macOS and Windows similarly runs Linux containers in a managed Linux environment. That means a command such as ps -fp <container-host-pid> on your physical host cannot be assumed to reveal the container process, because the daemon host is the Desktop VM.

The portable evidence path on Desktop is therefore Docker's own object inspection: docker context show, docker version, docker info, docker inspect, container-side /proc, and Desktop diagnostics. Keep the physical host, Desktop VM, Docker daemon, and container namespaces as four different layers when reasoning about failures.

11. Docker daemon access is a privilege boundary

The Docker daemon can create containers with host mounts, devices, capabilities, and other powerful settings. Docker's own post-installation documentation warns that membership in the docker group grants root-level privileges for a rootful daemon. Remote daemon credentials likewise deserve root-equivalent protection.

For Chapter 01, do not “solve” a socket permission error by using chmod 777, exposing plaintext TCP port 2375, or handing an untrusted account daemon access. Use an authorized disposable host, a properly administered rootful socket, or rootless Docker where appropriate. Later security chapters analyze these choices in depth.

12. The DevOps result is an evidence chain

Running a container is operationally meaningful only when you can explain the exact inputs and resulting state. For this first chapter, keep the evidence packet intentionally small:

Host/platform

OS, kernel, architecture, native Engine versus Desktop VM.

Client/daemon

Context, endpoint, client/server versions, negotiated API.

Image

Human tag used for discovery plus resolved immutable RepoDigest.

Container

Container ID, image config ID, running/exited state, host PID.

Isolation

Namespace links and cgroup membership where the platform exposes them.

Attachments

Network and mount identities from docker inspect.

13. Mini-lab — explain each successful state

With the disposable container still running, write one sentence for each statement below before checking the evidence: “the daemon is reachable,” “the image is present,” “the container object exists,” “the process is running,” and “the process is isolated.” Then run docker version, docker image inspect, docker container inspect, and the namespace/cgroup inspection appropriate to your platform. Correct any sentence that claimed more than its evidence proved.

docker image inspect "$ALPINE_REF" --format 'image-id={{.Id}} repo-digests={{json .RepoDigests}}'
docker container inspect "$LAB_CONTAINER" --format 'container={{.Id}} status={{.State.Status}} pid={{.State.Pid}}'
docker container inspect "$LAB_CONTAINER" --format 'networks={{json .NetworkSettings.Networks}}'
docker container inspect "$LAB_CONTAINER" --format 'mounts={{json .Mounts}}'

# Guarded cleanup: exact lab resource only.
docker rm -f "$LAB_CONTAINER"

Cleanup removes the exact named container, not “the latest container” and not unrelated images, volumes, or networks. The image can remain cached safely; if you choose to remove it, verify its exact digest first.

Knowledge check

A container prints a different /etc/os-release than the host. Does that prove it has a different kernel?

Why can PID 1 inside a container have a completely different PID on the host?

What does an OCI Runtime Specification implementation such as runc primarily consume?

A container is running. What does that prove about application health?

Why is Docker daemon access security-sensitive?

Summary

A Docker container is not a tiny VM. On Linux it is a process tree governed by namespaces, cgroups, filesystem/runtime configuration, and layered security controls while sharing the daemon host kernel. OCI specifications define portable image, runtime, and distribution contracts; Docker adds the Engine API, object model, build, networking, storage, security defaults, and UX around those contracts. The operational skill is to trace intent through client/context, daemon/runtime, image identity, container/process state, isolation, attachments, and evidence without collapsing them into “Docker ran.”

Next lesson

Next: Containers, Virtual Machines, Linux Isolation, OCI Standards, and Docker Architecture: Guided Hands-On Workflow and Core Operations

Continue with the next lesson in the course sequence and carry forward the evidence-first Docker operating model.

Official references and version notes

Version-sensitive statements in this chapter were rechecked against primary documentation on 2026-09-20. Docker Engine, Docker Desktop, containerd, runc, OCI specifications, security defaults, and platform integration continue to evolve. Record the versions actually reported by your own environment before treating an example as production policy.

Current-version note

Docker Engine 29.8.1 is the current Engine 29 patch release as of this lesson's verification date. The current Engine API page contains an example for 29.8.1 reporting API 1.56 while its version matrix lists Docker 29.8 with maximum API 1.55. This chapter therefore treats the output of docker version on the actual client/daemon pair as authoritative lab evidence and does not hard-code an expected negotiated API number.

Keep the academy open

Support free, practical DevOps education.

Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.

Ethereum / ERC-20
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0 Send only Ethereum/ERC-20 compatible assets to this address.