If you've ever run docker ps and wondered how a container manages to feel like its own little machine, with its own process list, its own network interface, its own hostname, the answer isn't magic and it isn't a virtual machine either. It's two Linux kernel features working together: namespaces and cgroups. One decides what a process can see. The other decides what it can use. Once you understand both, containers stop feeling like a black box and start looking like a clever combination of features that have been sitting in the kernel for years.

This article covers what namespaces and cgroups actually do, how they differ, and how you can peek at both on a running system.

What Linux namespaces actually do

A namespace wraps a global kernel resource so that a process (or group of processes) inside the namespace gets its own isolated view of that resource, separate from everything else on the machine. A process inside a PID namespace, for example, might think it's process 1, when on the host it's actually process 48213. It's not lying to itself; the kernel is genuinely presenting a different, scoped-down reality.

This idea has been part of Linux for a long time. According to the kernel's own documentation, namespaces were introduced incrementally starting around the 2.4.19 kernel with mount namespaces, and grew over the following decade to cover most of the resources a process might touch. The namespaces(7) man page is the most complete single reference if you want the details for each type.

There are seven namespace types in modern Linux, and it's worth knowing what each one isolates:

  • PID: gives processes their own view of the process tree, so a container can have its own "PID 1" independent of the host.
  • Network (net): isolates network interfaces, routing tables, and ports, which is why two containers can both bind to port 80 without conflict.
  • Mount (mnt): gives a process its own filesystem mount table, so mounting or unmounting inside a container doesn't touch the host.
  • UTS: isolates hostname and domain name, letting a container report its own hostname.
  • IPC: isolates System V IPC objects and POSIX message queues so unrelated processes can't interfere with each other's shared memory or semaphores.
  • User: maps user and group IDs differently inside and outside the namespace, so a process can be root inside a container without being root on the host.
  • cgroup: isolates the view of the cgroup hierarchy itself, so a process inside a container sees its own cgroup as the root rather than the full host hierarchy.

Namespaces are created with the clone(), unshare(), or setns() system calls, and a process can belong to a different combination of namespaces than its parent. That's really the whole trick: a container is just a regular Linux process that's been started inside a fresh set of namespaces instead of the ones its parent used.

What cgroups add to the picture

Namespaces solve visibility, but they don't solve resource contention. Nothing stops a process in its own PID and network namespace from consuming every CPU cycle and every byte of memory on the box. That's where control groups, better known as cgroups, come in.

cgroups were merged into the mainline kernel around version 2.6.24, and they let you organize processes into hierarchical groups and then apply limits, prioritization, and accounting to those groups as a whole. Instead of tracking resource usage per process, you can say "this group of processes gets 2 CPU cores and 512MB of RAM, total," and the kernel enforces it.

cgroups expose their controls through a virtual filesystem, typically mounted at /sys/fs/cgroup. Each controller (CPU, memory, block I/O, and others) shows up as files you can read and write directly, though in practice most people interact with cgroups through systemd or a container runtime rather than editing those files by hand. If your workflow already touches systemd unit files for service management, it's worth reading how init and systemd differ since systemd is what actually creates and manages cgroup hierarchies on most modern distributions.

cgroups v1 versus v2

There are two versions of cgroups in circulation, and it trips people up more than it should.

cgroups v1 gives each resource controller its own separate hierarchy. You could have memory limits organized one way and CPU limits organized a completely different way, which made combining them for a single application awkward and led to inconsistent naming between controllers.

cgroups v2, which the kernel documentation describes in detail, consolidates everything into a single unified hierarchy. All controllers attach to the same tree of groups, the control files are named consistently, and features like pressure stall information (a way to see how much a group is being throttled) were added along the way. Most current distributions default to cgroups v2 now, though you'll still find v1 on older systems and in some legacy container setups.

How namespaces and cgroups become a container

Put the two together and you get almost everything a container runtime needs. Docker, LXC, and the container layer underneath Kubernetes all follow roughly the same pattern: start a process in a fresh set of namespaces so it can't see other processes, other network interfaces, or the host filesystem, then drop that process into a cgroup so it can't starve the rest of the system of CPU or memory.

Neither piece does this alone. A process with its own namespaces but no cgroup limits is isolated but can still exhaust shared resources. A process under cgroup limits but with no namespace isolation can still see and potentially interfere with every other process on the host. Containers work because both mechanisms apply at once, and the runtime just automates the setup that you could otherwise do by hand with tools like unshare and cgcreate.

It's worth being clear that none of this is virtualization in the traditional sense. There's no hypervisor and no separate kernel; every container on a host shares that one kernel. That's a large part of why containers start in milliseconds and virtual machines take longer, but it also means a kernel-level vulnerability can, in principle, affect every container on the host. Namespaces and cgroups give strong isolation, but it's isolation within one kernel, not a hard security boundary the way a VM's hypervisor provides.

Inspecting namespaces and cgroups on a running system

This is the part that's easy to try yourself on any Linux box, no containers required.

To see which namespaces a process belongs to, check /proc/[pid]/ns:

ls -la /proc/1/ns

That directory lists a symlink for each namespace type (pid, net, mnt, uts, ipc, user, cgroup), each pointing to an inode number. Two processes sharing the same inode number for a given namespace type are, in fact, in the same namespace; different numbers mean different namespaces.

If you'd rather see everything at once, the lsns command gives you a readable table of every namespace currently in use on the system, what type it is, how many processes belong to it, and the command that started it:

lsns

Running that on a host with a couple of Docker containers active makes the isolation concrete. You'll see one set of PID and network namespaces for the host itself, and separate ones for each container, all coexisting on the same kernel.

For cgroups, you can look at a process's current group membership through /proc/[pid]/cgroup, and browse the actual controllers and limits under /sys/fs/cgroup. On a v2 system, something like this shows you the current memory limit for a given cgroup:

cat /sys/fs/cgroup/mygroup/memory.max

If none of this is unfamiliar territory and you're comfortable poking around a shell already, you might enjoy a closer look at what Bash actually is and how it compares to the other types of shells in Linux, since the terminal commands above are really just Bash talking to the kernel through /proc and /sys.

Conclusion

Namespaces and cgroups are two separate kernel features solving two separate problems, isolating what a process can see and limiting what it can use, and together they're the foundation nearly every container runtime is built on. Once you can find them in /proc and /sys/fs/cgroup yourself, the whole container ecosystem stops feeling like a mystery.

Thanks for reading! If you're looking for reliable infrastructure, xTom provides enterprise-grade dedicated servers and colocation services, while V.PS offers scalable, production-ready NVMe-powered VPS hosting perfect for any workload. You can also explore IP transit, shared hosting, and general IT services for whatever your infrastructure needs.

Ready to discuss your infrastructure needs? Contact our team to explore the right solution for your projects.

Frequently asked questions about Linux namespaces and cgroups

Are namespaces and cgroups the same thing as containers?

Not exactly. Namespaces and cgroups are kernel primitives; a container is a higher-level concept built on top of them by a runtime like Docker or LXC, which also adds things like image layers, networking configuration, and a defined filesystem root.

Do I need Docker installed to use namespaces or cgroups?

No. Both are built into the kernel itself. You can create namespaces with unshare and manage cgroups directly through the /sys/fs/cgroup filesystem without any container runtime at all, though runtimes make the process far more convenient.

Why do two containers show different process IDs for what looks like the same process?

Because each container has its own PID namespace. A process that appears as PID 1 inside a container is a normal, higher-numbered process on the host; the container just can't see the host's numbering scheme.

Is container isolation as strong as a virtual machine's?

Generally not. Containers share a single host kernel, so a serious kernel vulnerability can affect every container on that host. Virtual machines run separate kernels under a hypervisor, which gives a stronger isolation boundary, at the cost of more overhead per instance.

What happened to cgroups v1, is it deprecated?

cgroups v1 still works and plenty of systems run it, but it's effectively legacy at this point. cgroups v2 is the default on current major distributions and is where new kernel features are being added, so new deployments should generally target v2 unless something specific requires v1.