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.
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.
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.
/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.
Changes the process-ID view. PID 1 inside the container is not normally host PID 1.
Gives the process a distinct mount view assembled from image layers and explicit mounts.
Provides interfaces, routes, sockets, and firewall/NAT relationships distinct from the host namespace.
Allows a container-specific hostname/domain-name view.
Account for and optionally limit CPU, memory, PIDs, and other resources. Current Docker documentation recommends cgroup v2 migration.
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.
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.
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:
OS, kernel, architecture, native Engine versus Desktop VM.
Context, endpoint, client/server versions, negotiated API.
Human tag used for discovery plus resolved immutable RepoDigest.
Container ID, image config ID, running/exited state, host PID.
Namespace links and cgroup membership where the platform exposes them.
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?
No. The file comes from the container filesystem/userspace. On native Linux Engine, the container still uses the daemon host kernel; on Docker Desktop it uses the Desktop VM kernel.
Why can PID 1 inside a container have a completely different PID on the host?
PID namespaces give processes a different process-ID view. The same underlying process can be PID 1 inside the container namespace and another PID in the host namespace.
What does an OCI Runtime Specification implementation such as
runc primarily consume?
An OCI runtime bundle, including a root filesystem and runtime
configuration such as config.json, to create and
manage the container process lifecycle.
A container is running. What does that prove about application health?
Only that the container main process is currently running. It does not prove readiness, end-user reachability, data durability, or external dependency health.
Why is Docker daemon access security-sensitive?
The daemon can create highly privileged containers and expose host resources. Rootful daemon access is therefore effectively a host-privilege boundary and must not be handed to untrusted users or code.
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.”
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.
- Docker documentation — product documentation and current operating guidance.
- Docker Engine — Engine architecture, daemon, objects, and administration.
- Docker Engine 29 release notes — current 29.x changes; 29.8.1 was released 2026-09-15.
- Docker Engine API — API negotiation and current client/server compatibility guidance.
- Docker Engine security — namespaces, cgroups, daemon attack surface, and kernel hardening.
- Seccomp security profiles — default syscall filtering and why disabling it is not a routine fix.
- Runtime metrics — current cgroup v2 behavior and container cgroup inspection.
- Rootless mode — daemon/container privilege reduction and user-namespace model.
-
Docker Desktop on Linux
— the VM-backed Desktop execution boundary and
desktop-linuxcontext. - Open Container Initiative — Image, Runtime, and Distribution specifications.
- OCI Runtime Specification — runtime configuration, execution environment, and lifecycle; OCI Runtime Spec v1.3.0 was released in November 2025.
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.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0
Send only Ethereum/ERC-20 compatible assets to this
address.