A Docker container isn’t a virtual machine. It’s a set of ordinary Linux processes that the kernel shows a restricted view of the system: their own process list, network interfaces, hostname, and filesystem. That isolation comes from namespaces. Combined with cgroups (resource limits) and capabilities (privilege limits), namespaces are what make containers possible.
The namespace types
| Namespace | Isolates | Effect inside the namespace |
|---|---|---|
| PID | Process IDs | The container sees its own processes, starting at PID 1 |
| Network (net) | Interfaces, IPs, routes, firewall rules, ports | Its own eth0, its own port 80 |
| Mount (mnt) | Mount points | Its own root filesystem and mounts |
| UTS | Hostname and domain name | Its own hostname |
| IPC | Shared memory, message queues | Separate IPC objects |
| User | User and group IDs | Root inside can map to an unprivileged user outside |
| Cgroup | View of the cgroup hierarchy | Sees its own cgroup as the root |
| Time | Some system clocks | Separate boot and monotonic clocks (rarely used) |
See namespaces on your server
lsns # all namespaces and their processes
lsns -t net # network namespaces only
ls -l /proc/$$/ns # namespaces of your current shell
sudo ls -l /proc/$(docker inspect -f '{{.State.Pid}}' nginx)/ns # a container's namespaces
Processes that share a namespace show the same inode number for it.
Try it: unshare
unshare runs a program in new namespaces:
# new UTS namespace: change the hostname without affecting the system
sudo unshare --uts bash -c 'hostname sandbox; hostname'
hostname # unchanged
# new PID + mount namespace: ps only shows processes inside
sudo unshare --pid --fork --mount-proc bash
ps aux # bash is PID 1
exit
Enter a namespace: nsenter
nsenter runs a command inside an existing process’s namespaces, which is useful for debugging containers that have no shell or tools:
PID=$(docker inspect -f '{{.State.Pid}}' web)
sudo nsenter -t "$PID" -n ip addr # container's network view, with host tools
sudo nsenter -t "$PID" -n ss -tlnp # what's listening inside
sudo nsenter -t "$PID" -m -u -i -n -p sh # (almost) like docker exec
Network namespaces
Network namespaces give each container (or VPN or test environment) its own network stack. They’re also usable directly with ip netns. See Linux network namespaces.
User namespaces and rootless containers
With a user namespace, a process can be root (UID 0) inside, while being an unprivileged user (for example UID 100000) on the host. That’s the basis of rootless Docker and Podman: even if an attacker escapes the container, they don’t have host root. See Docker rootless mode.
unshare --user --map-root-user bash -c 'id' # uid=0(root) — but only inside
Security implications
- Namespaces provide isolation, not a hard security boundary like a hypervisor. Containers share the host kernel, so a kernel vulnerability can allow escapes. That’s why untrusted workloads are better isolated in separate VMs. See Docker vs virtual machine and what is KVM virtualization.
- Avoid
--privileged,--pid=hostand--net=hostunless needed. They remove isolation layers. - Sharing the host network namespace (
--network host) also means container ports bypass Docker’s port mapping. See Docker and UFW.
Namespaces + cgroups + capabilities
| Feature | Controls |
|---|---|
| Namespaces | What a process sees |
| cgroups | How much it can use. See Linux cgroups |
| Capabilities | Which privileged actions it may do. See Linux capabilities |
| seccomp, AppArmor/SELinux | Which syscalls and resources it may access |
Quick reference
| Task | Command |
|---|---|
| List namespaces | lsns |
| A process’s namespaces | ls -l /proc/<pid>/ns |
| Run in new namespaces | unshare --uts --pid --fork … |
| Enter a container’s namespaces | nsenter -t <pid> -n … |
| Network namespaces | ip netns |
Related: Docker Compose tutorial and Kubernetes security basics.