Linux Namespaces Explained: What a Container Actually Is

Linux Namespaces Explained: What a Container Actually Is

There is no container object in the Linux kernel. Nothing anywhere maintains a list of containers.

A container is an ordinary process that was started with a restricted view of the system. Container runtimes assemble that restriction out of primitives the kernel has had for years, and once you know which primitives, the whole thing stops being mysterious.

Namespaces restrict what a process can see

Each namespace type virtualizes one global resource.

PID. The process gets its own process table starting at PID 1. It cannot see or signal processes outside it. This is why ps in a container shows three processes on a host running four hundred.

Mount. Its own filesystem view. Mounts made inside are invisible outside and the reverse.

Network. Its own interfaces, routing table, firewall rules, and ports. Two containers can both bind port 80 without conflict because they are different ports in different namespaces.

UTS. Its own hostname and domain name.

IPC. Its own System V IPC and POSIX message queues.

User. Its own UID and GID mapping, so UID 0 inside can be UID 100000 outside.

cgroup. Its own view of the cgroup hierarchy.

Time. Its own boot time and monotonic clock offsets.

# What namespaces does a process have?
ls -l /proc/self/ns/
lsns
lsns -p 1234

Build one by hand

The fastest way to understand this is to make one:

sudo unshare --pid --fork --mount-proc --uts --net --ipc bash

# now, inside:
hostname container-demo    # UTS namespace, host is unaffected
ps aux                     # PID namespace, almost nothing here
ip a                       # network namespace, just loopback
exit

That is most of a container. Add a root filesystem with chroot or pivot_root, add a cgroup for limits, and you have essentially reimplemented Docker’s core.

cgroups are the other half

Namespaces control what you can see. cgroups control how much you can use: memory, CPU, I/O, process count.

They are frequently conflated and they solve unrelated problems. A process can be perfectly isolated and still consume all the memory on the machine, which is why containers need both.

User namespaces and rootless containers

The most consequential one for security.

A user namespace maps UIDs. Root inside can be an unprivileged user outside. A process can therefore have full capabilities within its own namespace while holding none on the host.

That is exactly what makes rootless containers possible, and it is why Podman can run containers as your ordinary user where Docker traditionally required a root daemon.

cat /proc/self/uid_map
# 0    100000    65536

That mapping says UID 0 in here is UID 100000 out there.

What this explains

Knowing the primitives makes container behaviour predictable rather than surprising:

  • Why a container sees the host kernel version. There is only one kernel. uname -r reports it because nothing virtualizes it.
  • Why containers cannot load kernel modules. Modules are global; namespaces do not virtualize them.
  • Why two containers can bind the same port. Different network namespaces.
  • Why a container escape is possible at all. Everything is one kernel, and a kernel bug crosses every namespace.
  • Why containers start instantly. Nothing boots. A process starts with different flags.

The isolation question

Namespace isolation is good and it is not a virtual machine.

A VM has its own kernel, and the interface between guest and host is a small hypervisor surface. A container shares your kernel, and the interface is the entire system call surface.

For workloads you control, containers are a reasonable boundary. For genuinely untrusted code, use a virtual machine or something like Kata Containers that puts a lightweight VM underneath the container interface.

Frequently Asked Questions

Does the kernel have a concept of a container?

No. There is no container object in the kernel. A container is an ordinary process that was started with several namespaces, placed in a cgroup, and given a different root filesystem. Container runtimes assemble those primitives; the kernel just sees processes.

What do namespaces actually isolate?

Each namespace type restricts the view of one kind of global resource: PID namespaces for the process table, mount for the filesystem view, network for interfaces and ports, UTS for the hostname, IPC for shared memory, user for UID mapping, and cgroup for the cgroup hierarchy.

How are namespaces different from cgroups?

Namespaces control what a process can see. cgroups control how much it can use. They solve different problems and containers use both, which is why a container can be isolated from other processes and still be limited to one gigabyte of memory.

What makes user namespaces special?

They map UIDs, so root inside the namespace can be an unprivileged user outside it. That is what makes rootless containers possible, because a process can have full privileges within its own namespace while holding none on the host.

Can I create namespaces without a container runtime?

Yes. The unshare command creates them directly, and nsenter joins existing ones. Running unshare with a few flags gives you something recognisably container-like in one line, which is the clearest way to understand what runtimes are doing.

Is namespace isolation as strong as a virtual machine?

No. Everything shares one kernel, so a kernel vulnerability crosses the boundary. A VM has its own kernel and a much smaller attack surface between guest and host. Namespaces are a good boundary and not the same boundary.