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
- OSUbuntu 26.04 LTS (resolute)
- Kernel7.0.0-29-generic
- Docker Engine29.7.2
- Architectureamd64
- TimeAbout 13 min
- Reviewed20 August 2026
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.
| Server Name | IP Address | OS | Roles | CPU | RAM | HDD |
|---|---|---|---|---|---|---|
| DOCKER01 | 192.168.0.21 | Ubuntu 26.04 LTS | Docker Host | 2 Core | 4 GB | 50 GB |
Before you start
- You can run a container - guide 5 comes next and does not depend on this one.
- The client/daemon and image/container distinctions - guide 2 in this path.
-
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-genericExpected 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.
-
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=alpineExpected 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.
-
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,ipcandcgroupall 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.
-
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.
-
Watch a namespace being removed
The clearest way to see what a namespace does is to take one away.
--pid=hostputs 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.
-
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.
-
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-nsExpected 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
A Linux image will not run on Windows or macOS
Why: Containers share the host kernel, and there is no Linux kernel on those systems. Docker Desktop runs a Linux VM and your containers run inside it.
Fix:Nothing to fix - understand that on those platforms there is a VM in the path, which is why filesystem performance and networking behave differently there.
bash docker info --format "{{.OperatingSystem}} / {{.OSType}}"exec format error when running an image
Why: The image was built for a different CPU architecture. The shared kernel runs the binary directly, so it must match the machine.
Fix:Pull or build for the right architecture - see guide 23.
bash docker image inspect IMAGE --format "{{.Os}}/{{.Architecture}}"uname -mA container can see host processes or devices you did not expect
Why: A namespace was disabled -
--pid=host,--net=host,--ipc=host- or the container is--privileged, which removes most restrictions at once.Fix:Audit the flags. Each of those is a deliberate hole in the isolation and should be justified per container.
bash docker inspect NAME --format "pid={{.HostConfig.PidMode}} net={{.HostConfig.NetworkMode}} privileged={{.HostConfig.Privileged}}"Files written by a container are owned by root on the host
Why: No user namespace by default, so uid 0 in the container is uid 0 outside it. A bind-mounted directory gets root-owned files.
Fix:Run the container as your own uid with
--user $(id -u):$(id -g), or enable user-namespace remapping on the daemon.bash docker run --rm --user $(id -u):$(id -g) -v "$PWD:/w" alpine:3.22 touch /w/file