Containers are just processes in namespaces; understanding them demystifies Docker and Kubernetes debugging.
A namespace wraps a global resource so that processes inside see their own isolated instance. Linux has eight kinds:
- PID: its own process numbering; the first process is PID 1 inside,
- network: own interfaces, IPs, routes, firewall rules and ports,
- mount: own view of the mount table, so a container sees its own root filesystem,
- UTS: own hostname; IPC: own System V IPC and POSIX message queues,
- user: own UID/GID mappings, so a process can be root inside and an unprivileged user outside (the basis of rootless containers),
- cgroup and time namespaces virtualise the cgroup tree view and some clocks.
Every process belongs to one namespace of each kind; /proc/<PID>/ns/ shows them as links whose numbers identify the namespace, and lsns lists them system-wide.
unshare starts a program in new namespaces (--pid --fork --mount-proc, --net, --user --map-root-user), and nsenter -t PID -n (or -a for all) runs a command inside another process's namespaces. That is extremely useful for containers: you can use the host's tools, such as ss, ip or tcpdump, inside a container's network namespace even if the image has none.
A container is roughly: namespaces for isolation, cgroups for resource limits, a layered root filesystem, capabilities and seccomp filters to restrict privileges. There is no "container" object in the kernel.
Example
ls -l /proc/$$/ns | tail -n +2 | head -n 3 # cgroup, ipc, mnt ... -> 'net:[4026531840]'
sudo unshare --pid --fork --mount-proc ps -o pid,cmd
# PID CMD
# 1 ps -o pid,cmd <- new PID namespace: only this process, as PID 1
sudo unshare --net ip -br link # lo DOWN (an empty network stack)
unshare --user --map-root-user id -u # 0 (root inside, no sudo needed)
sudo unshare --uts bash -c 'hostname sandbox; hostname' # sandbox (host unchanged)
lsns -t net # network namespaces in use
sudo ip netns add blue && sudo ip netns exec blue ip -br link # lo DOWN
pid=$(docker inspect -f '{{.State.Pid}}' web)
sudo nsenter -t "$pid" -n ss -tlnp # host's ss inside the container's netnsRemember
- Namespaces isolate PIDs, network, mounts, hostname, IPC, users, cgroups and time
- unshare creates namespaces; nsenter joins a process's namespaces
- A container = namespaces + cgroups + root filesystem + restricted privileges
Watch out for
- Installing debugging tools inside a running container image instead of entering its namespaces with nsenter.