Skip to main content
RunBook Academy

Docker & ContainersI Β· FoundationsLinux primitives

The Linux primitives behind a container

Foundation⏱ ~20 min

What you'll learn

  • Name the five kernel primitives that together make a container
  • Locate each one on a running container from the host
  • Explain which host tools work on containers, and why
  • Recognise which primitive is responsible for a given symptom

Prerequisites

Verified against Docker Engine 29.x Β· Docker Engine 28.x Β· Docker Compose 2.x Β· containerd 2.x Β· runc 1.2.x Β· BuildKit 0.20+ Β· Linux kernel 5.15+ Β· Ubuntu 24.04 LTS Β· Debian 12 (Bookworm) Β· 2026-08-11

Not yet marked complete on this device.

The containers-versus-VMs lesson made a claim: a container is a Linux process with kernel features wrapped around it. This lesson makes that claim concrete by taking one running container and pointing at every feature in turn, from the host.

You do not need to master any of them here. Part II of this course takes each apart in detail. What you need by the end of this lesson is the map β€” five names, what each one does, and where to look β€” so that later parts have somewhere to attach.

The five primitives

PrimitiveKernel featureGovernsWhere to look
Namespacesclone(2) flagsWhat the process can see/proc/PID/ns/
cgroupscgroup v2 hierarchyWhat it may use/sys/fs/cgroup/...
Capabilitiescapabilities(7)What it may do as root/proc/PID/status
Union filesystemOverlayFSWhat its root filesystem isfindmnt, docker inspect
Syscall filteringseccomp, AppArmor, SELinuxWhich syscalls it may make/proc/PID/status

Docker is none of these. Docker is the program that decides which values to use and asks the kernel to apply them. Every one of them predates Docker and is usable without it.

The tour

Start a container and find its process ID on the host. That number is the thread that connects everything below.

Configuration changea container to look at
$ docker run -d --name web --memory 256m --cpus 0.5 nginx:1.29-alpine
b7d2a91f4e6c8a3d05b1c9e7f2a48d6031e5b7c9a0d2f4e68b1c3a5d7e9f0b2c4

Illustrative output

Read-only / Safethe host PID of the container's init process
$ docker inspect web --format '{{.State.Pid}}'
184213

Illustrative output

It is an ordinary process

Read-only / Safenginx, in the host process table
$ ps -o pid,ppid,user,rss,args -p 184213
    PID    PPID USER       RSS COMMAND
184213  184190 root      4212 nginx: master process nginx -g daemon off;

Illustrative output

There is nothing container-shaped about that line. ps lists it, kill can signal it, renice can reprioritise it, strace can trace it. The host’s process table is the real process table; the one inside the container is a filtered view of it.

Namespaces β€” what it can see

A namespace is a kernel-maintained scope, identified by an inode number. Two processes in the same namespace share that view of the world; two in different namespaces do not.

Read-only / Safethe container's namespaces
$ sudo ls -l /proc/184213/ns/
lrwxrwxrwx 1 root root 0 Aug 11 09:14 cgroup -> cgroup:[4026532614]
lrwxrwxrwx 1 root root 0 Aug 11 09:14 ipc -> ipc:[4026532552]
lrwxrwxrwx 1 root root 0 Aug 11 09:14 mnt -> mnt:[4026532550]
lrwxrwxrwx 1 root root 0 Aug 11 09:14 net -> net:[4026532617]
lrwxrwxrwx 1 root root 0 Aug 11 09:14 pid -> pid:[4026532553]
lrwxrwxrwx 1 root root 0 Aug 11 09:14 time -> time:[4026531834]
lrwxrwxrwx 1 root root 0 Aug 11 09:14 user -> user:[4026531837]
lrwxrwxrwx 1 root root 0 Aug 11 09:14 uts -> uts:[4026532551]

Illustrative output

Compare with your own shell:

Read-only / Safeyour shell's namespaces, for contrast
$ readlink /proc/self/ns/pid /proc/self/ns/mnt /proc/self/ns/net /proc/self/ns/user
pid:[4026531836]
mnt:[4026531832]
net:[4026531833]
user:[4026531837]

Illustrative output

Three of those numbers differ and one β€” user β€” is identical. That single detail is the most important thing on this page: without user namespaces enabled, root inside the container is root on the host, the same UID 0, in the same user namespace. Everything that stops it behaving like root is capabilities and syscall filtering, not identity.

lsns lists namespaces system-wide, which is the quickest way to see which processes share which:

Read-only / Safewho shares a network namespace
$ sudo lsns -t net
        NS TYPE NPROCS    PID USER  COMMAND
4026531833 net     412      1 root  /sbin/init
4026532617 net       3 184213 root  nginx: master process nginx -g daemon off;

Illustrative output

cgroups β€” what it may use

The --memory 256m and --cpus 0.5 from the docker run line above became kernel settings. Follow the pointer:

Read-only / Safewhich cgroup this process is in
$ cat /proc/184213/cgroup
0::/system.slice/docker-b7d2a91f4e6c8a3d05b1c9e7f2a48d6031e5b7c9a0d2f4e68b1c3a5d7e9f0b2c4.scope

Illustrative output

Read-only / Safethe limits actually in force
$ cd /sys/fs/cgroup/system.slice/docker-b7d2a91f*.scope && grep . memory.max memory.current cpu.max pids.max
memory.max:268435456
memory.current:5185536
cpu.max:50000 100000
pids.max:max

Illustrative output

268435456 is 256 MiB. 50000 100000 means β€œ50 ms of CPU time per 100 ms period”, which is --cpus 0.5. The flags you typed are a friendly front end to these files, and the files are the truth.

Capabilities β€” what it may do as root

Read-only / Safethe effective capability set
$ grep CapEff /proc/184213/status
CapEff:	00000000a80425fb

Illustrative output

Read-only / Safedecode the bitmask
$ capsh --decode=00000000a80425fb
0x00000000a80425fb=cap_chown,cap_dac_override,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_net_raw,cap_sys_chroot,cap_mknod,cap_audit_write,cap_setfcap

That is Docker’s default set: fourteen capabilities out of roughly forty. The absent ones are the interesting list β€” CAP_SYS_ADMIN, CAP_SYS_MODULE, CAP_SYS_PTRACE, CAP_NET_ADMIN β€” which is why a container cannot load a kernel module or reconfigure the host’s firewall despite running as root.

--privileged replaces that mask with all of them, which is the whole reason the flag is dangerous.

The root filesystem

Read-only / Safewhat the container's root actually is
$ docker exec web head -1 /proc/mounts
overlay / overlay rw,relatime,lowerdir=/var/lib/docker/overlay2/l/K3F...,upperdir=/var/lib/docker/overlay2/9a1.../diff,workdir=/var/lib/docker/overlay2/9a1.../work 0 0

Illustrative output

(/proc/mounts rather than findmnt, because Alpine-based images do not carry findmnt.)

An OverlayFS mount composed of the image’s read-only layers plus one writable layer created for this container. Deleting the container discards the writable layer, which is the mechanical reason container filesystems are ephemeral and volumes exist.

Syscall filtering

Read-only / Safeis a seccomp filter attached?
$ grep -E '^Seccomp' /proc/184213/status
Seccomp:	2
Seccomp_filters:	1

Illustrative output

Mode 2 means a BPF filter is installed β€” Docker’s default seccomp profile, which blocks several dozen syscalls that a normal workload never issues. A 0 here means the container is running unconfined, which is what --security-opt seccomp=unconfined and --privileged both produce.

Why this map is worth carrying

  • A container is not reachable by any special tooling. strace, perf, gdb, ss, lsof and kill all work on the host PID. When Docker tooling is unhelpful, drop to the process.
  • Symptoms map to primitives. "Killed with no log" is cgroups (memory). "Operation not permitted as root" is capabilities or seccomp. "It cannot see the other container" is namespaces. "The data vanished" is the writable layer.
  • Limits are files. Anything docker run set, you can read back under /sys/fs/cgroup and verify rather than trust.
  • The kernel is shared. One kernel, one attack surface, one set of tunables. A sysctl is a host-wide decision even when only one container needs it.

Clean up when you are finished:

Destructiveremove the demonstration container
$ docker rm -f web
web

Knowledge check

Knowledge check Β· 4 questions

  1. Q1. A container running as root cannot load a kernel module, despite being UID 0. Which primitive is responsible?

  2. Q2. You set `--memory 256m` on a container. Where can you read back the value the kernel is actually enforcing?

  3. Q3. Which statements about a container process are true when viewed from the host? Select all that apply.

  4. Q4. Without user namespaces enabled, root inside a container is the same UID 0 as root on the host.

Passing score: 75%. Answers are checked in this browser.

Where next

That closes the foundations. Part II takes each primitive on this map and works through it in depth, starting with namespaces.