CertGrid CertGrid
Concepts·Docker

Docker Container Process Model

A container is an ordinary Linux process with a restricted view of the machine. Prove it in six commands: same kernel as the host, different userspace, its own namespaces, and a PID you can see from outside.

Getting Started Guide 3 of 46 Beginner

Tested on the versions above. Namespace inode numbers and PIDs are different on every machine and every run. Match the shape - which numbers differ and which match - not the values.

One Docker host is all this guide needs. Nothing here depends on a second machine, and the hardware above is modest on purpose - a 2 core, 4 GB VM runs everything in this path.
Server NameIP AddressOSRolesCPURAMHDD
DOCKER01192.168.0.21Ubuntu 26.04 LTSDocker Host2 Core4 GB50 GB

Before you start

  1. It is not a small virtual machine

    A virtual machine runs its own kernel on virtualised hardware. A container does not: it uses the host's kernel directly. This single command settles it - the host and the container report the SAME kernel version, because there is only one kernel running on the machine.

    bash Example session
    uname -r7.0.0-29-genericdocker run --rm alpine:3.22 uname -r7.0.0-29-generic

    Expected resultIdentical kernel versions.

    Success conditionThe numbers match. If this were a VM they could not - a VM boots a kernel of its own. This is also why a Linux container cannot run on a Windows kernel without a Linux VM underneath.

  2. But the userspace is completely different

    So if the kernel is shared, what makes it feel like another machine? The filesystem. The image supplies a complete userspace - libraries, package manager, /etc - and the process sees that instead of the host's. Ubuntu outside, Alpine inside, one kernel serving both.

    bash
    cat /etc/os-release | head -2PRETTY_NAME="Ubuntu 26.04 LTS"NAME="Ubuntu"docker run --rm alpine:3.22 cat /etc/os-release | head -2NAME="Alpine Linux"ID=alpine

    Expected resultTwo different distributions.

    Success conditionYou can state the split: the image gives you a distribution, the host gives you the kernel. That is the whole trick.

  3. Namespaces: the restricted view

    A namespace is a kernel feature that gives a process its own copy of some global resource. The kernel exposes them as links in /proc, and the numbers in brackets identify them. Compare the container's with the host's - mnt, net, pid, uts, ipc and cgroup all differ, which is precisely why the container sees its own filesystem, network and process list.

    bash Example session
    docker run --rm alpine:3.22 ls -l /proc/self/ns/cgroup -> cgroup:[4026532333]ipc -> ipc:[4026532329]mnt -> mnt:[4026532162]net -> net:[4026532334]pid -> pid:[4026532332]user -> user:[4026531837]uts -> uts:[4026532163]

    Expected resultDifferent identifiers for each namespace the container has its own of.

    Verify it worked

    bash Example session
    ls -l /proc/self/ns/ | head -6cgroup -> cgroup:[4026531835]ipc -> ipc:[4026531839]mnt -> mnt:[4026531832]net -> net:[4026531833]pid -> pid:[4026531836]

    Success conditionThe numbers differ. Note ONE that does not, covered in the next step - and it is the security-relevant one.

  4. The user namespace is shared by default

    Look carefully: the container reported user -> user:[4026531837], and so does the host. Docker does not enable the user namespace by default, which means uid 0 inside the container is uid 0 on the host. The other namespaces stop the container SEEING host resources; they do not make its root a different root. That is the single most important sentence in this guide, and the reason for guide 39 later in this path.

    bash Example session
    docker run --rm alpine:3.22 readlink /proc/self/ns/user; readlink /proc/self/ns/useruser:[4026531837]user:[4026531837]

    Expected resultThe same user namespace identifier from inside and outside.

    Success conditionThey match. Root in a default container is host root wearing a blindfold, not a different account.

  5. Watch a namespace being removed

    The clearest way to see what a namespace does is to take one away. --pid=host puts the container in the host's PID namespace, and suddenly it can see every process on the machine. Nothing else about the container changed.

    bash Example session
    docker run --rm alpine:3.22 ps auxPID   USER     TIME  COMMAND    1 root      0:00 ps aux# now without the PID namespace:docker run --rm --pid=host alpine:3.22 ps aux | head -4    1 root      0:01 /usr/lib/systemd/systemd --switched-root --system --deserialize=50    2 root      0:00 [kthreadd]    3 root      0:00 [pool_workqueue_]

    Expected resultOne process normally; the host's entire process table with --pid=host.

    Success conditionYou have seen isolation switched off. Also note the normal case: your process is PID 1, which has consequences covered in guide 8.

  6. From the host, it is just a process

    This is the payoff. The container's process appears in the host's own process table with an ordinary PID, owned by root, running the command from the image. There is no boundary object called a container - the container IS this process plus the namespaces and cgroup attached to it.

    bash Example session
    docker run -d --name cg-ns --memory 96m nginx:alpinedocker inspect cg-ns --format "{{.State.Pid}}"29981ps -o pid,user,args -p 29981    PID USER     COMMAND  29981 root     nginx: master process nginx -g daemon off;

    Expected resultA normal host process, whose namespace links match the ones the container reported.

    Verify it worked

    bash Example session
    sudo ls -l /proc/29981/ns/ | grep -E "mnt|net|pid"mnt -> mnt:[4026532162]net -> net:[4026532334]pid -> pid:[4026532332]

    Success conditionSame namespace numbers from both sides. You are looking at one process from two angles - that is the entire concept.

  7. cgroups: the resource half

    Namespaces control what a process can SEE. Control groups - cgroups - control what it can USE. The memory ceiling you set on the command line is written into a kernel file the process can read, and the kernel enforces it. Namespaces plus cgroups plus an image filesystem is, functionally, the whole definition of a container.

    bash
    docker exec cg-ns cat /sys/fs/cgroup/memory.max100663296# 100663296 bytes = 96 MiB, the --memory value from the run commanddocker rm -f cg-ns

    Expected resultThe limit you set, in bytes, readable from inside the container.

    Success conditionYou can name the three pieces: namespaces for isolation, cgroups for limits, an image for the filesystem. guide 30 builds on the cgroup half.

Troubleshooting

Official sources