Namespaces and cgroups in Linux: the real foundation of containers

Last update: February 24th 2026
  • Containers in Linux rely on namespaces to isolate what processes can see and on cgroups to limit how many resources they can use.
  • There are different types of namespaces (PID, NET, MNT, UTS, IPC, USER, cgroup, time) and cgroup controllers (CPU, memory, I/O, pids) that are combined depending on the case.
  • Docker, Kubernetes, and systemd rely on these kernel primitives to create isolated environments, enforce resource policies, and manage services uniformly.
  • Monitoring cgroup metrics and understanding namespace topology is essential for diagnosing OOMs, performance issues, and ensuring solid isolation in production.

linux containers cgroups namespaces

If you work with Docker, Kubernetes , or any container platform, sooner or later you'll have to delve into the system's inner workings. Behind the "magic" of launching containers in seconds lie two key components of the Linux kernel: namespaces and cgroups, the duo responsible for isolation and fine-tuned resource management . Understanding them makes all the difference between simply using tools and designing the infrastructure.

Throughout this article we will calmly but without going into too much detail how they work namespaces (what a process can see) and cgroups (what a process can use)How Docker and Kubernetes take advantage of them; how you can tinker with them at a low level using commands like unshare, ip netns or by writing directly in /sys/fs/cgroupAnd what tricks are useful for performance, security, and troubleshooting in production environments.

What does a container actually do in Linux?

When you run something like docker runa magic virtual machine is not created; What you get is a Linux process (or a group of processes) with strong isolation and limited resources.The “trick” is based on four kernel mechanisms:

  • Namespaces: They control what the process sees (PIDs, network, file system, hostname, etc.).
  • CgroupsThey control how much the process can use (CPU, RAM, I/O, PIDs…).
  • Chroot / rootfs isolated: they give the process its own file system.
  • Normal Linux processesIn the end, it all comes down to host PIDs managed by the kernel.

In other words, a container is simply a process running with a set of new namespaces, nested within one or more cgroups, and with a custom-built file system . The rest is handled by the tool (Docker, containerd, runc, etc.) with automation and user-friendly APIs.

Linux namespaces: isolating what each process can see

Namespaces are a kernel abstraction layer that creates separate "worlds" for groups of processes . A process only sees the resources within its namespace; the rest of the system is still there, but invisible to it.

Linux currently has several types of namespaces, each focused on a specific part of the system. Combined, they allow a container to be perceived as an independent operating system even though it shares a kernel with the host.

Main types of namespaces

Each type of namespace isolates a different dimension. It's common practice in a "serious" container to use several of them simultaneously. These are the most important:

  • PID namespace: isolates the process identifier space. Within the container, the main process is usually PID 1even if the host has a different number. Processes from different namespaces can have the same PID without interfering with each other, and they cannot send signals to each other unless explicitly permitted by the host.
  • Network (NET) namespace: creates an independent network stack, with its own interfaces, routes, firewall rules, sockets, etc. Each container has its own loopback, its own IPs, and its own routingThis way, you can reuse the same IP address in different namespaces without conflict and apply isolated iptables rules.
  • Mount (MNT) namespace: separates the view from the mounting points. Processes only see the file hierarchy of their namespaceEven though there are bind mounts, shared volumes, overlays, etc. underneath. This is what allows a container to appear to have its own "/" without touching the host's actual root.
  • UTS namespace: controls the hostname and NIS domain. Within a UTS namespace, You can change the hostname without affecting the host systemVery useful for distinguishing containers or simulating nodes in test environments.
  • IPC namespace: isolates System V message queues, semaphores, and shared memory. Prevent different container processes from interfering with the IPC due to accident or poor design.
  • User namespaceProbably the most delicate. It allows Map internal namespace UIDs and GIDs to other host IDsThis makes it possible for the "root" within the container to be mapped to an unprivileged user on the host, greatly reducing the risk of privilege escalation and improving the container security.
  • Cgroup namespace: provides a virtualized view of /proc/self/cgroup and the cgroups hierarchy. From inside the container, only its own groups are visible.This hides the actual structure of the host and reinforces isolation.
  • Time namespace: allows offering different perceptions of time (clocks like CLOCK_MONOTONIC or CLOCK_BOOTTIME) for different process groups. It is useful for testing scenarios or very fine isolation.
  The 5 most common operating systems today

All these namespaces are exposed in /proc/<pid>/ns/and can be created or combined with tools such as unshare y nsenterWhen the last process belonging to a namespace dies, that namespace is automatically destroyed.

Experimenting with namespaces from the command line

The command unshare It's one of the basics for experimenting: creates a child process with one or more new namespacesFor example, to test an isolated PID namespace:

unshare --pid --fork --mount-proc bash
ps aux
echo $$

Inside, you'll only see the processes from that namespace, and your shell will appear as PID 1 there, even though the host will still have all its usual processes. It's basically the skeleton of what an OCI runtime will do when a container starts.

Network Namespaces in Detail

The networking aspect is one of the most versatile. When creating a NET namespace with ip netns addYou get an empty stack except for the loopback stack. From there you can:

  • Connect that namespace to the host with a pair veth (two virtual interfaces joined by a logical cable).
  • Create bridges type br0 and connect several namespaces to that bridge so that they can see each other using private IPs.
  • NAT and forwarding to give internet access to those "homemade containers" with iptables and /proc/sys/net/ipv4/ip_forward.

That's right, at a very low level, that's what Docker does with the bridge docker0 and their default networks: It sets up network namespaces, veth peers, bridges, and NAT rules. so that the containers can talk to each other and go outside.

Mount and user namespaces for rootfs and security

With `mount namespace` you can experiment with creating a container filesystem without using Docker. The typical pattern is:

  • To assemble or prepare a minimum rootfs (for example, an Alpine minirootfs downloaded by wget and extracted to a directory).
  • Create a mount namespace and do pivot_root o chroot to that directory.
  • Mount /proc, /sys and other pseudo-systems within the new root.

With that, your shell will only see that filesystem, but You remain a normal host process, with your real PID and controllable by the administrator.If you also use a user namespace with --map-root-userYou can have a "root" inside that is outside a user without privileges.

Cgroups: how much resource each process can spend

While namespaces determine what a process can see, cgroups limit how many system resources a group of processes can consume . They were introduced in kernel 2.6.24 and are now essential on any modern container platform.

At a conceptual level, a cgroup is a directory within /sys/fs/cgroup with files that describe limits, priorities, and metricsWhen you add a PID to the appropriate file (for example, cgroup.procs), that process is subject to those rules.

Resource controllers in cgroups

Cgroups are composed of controllers (subsystems) that act on specific resources. Among the most commonly used are:

  • CPU: controls allocated CPU time. With shares defines relative priority; with quotas hard limits (e.g., 50 ms CPU timeout per 100 ms period); with CPUsets You fix processes to specific cores.
  • Memory: limits RAM and swap. Allows Hard and soft limits, OOM killer settings, statistics, and memory pressureVery useful for preventing a service from consuming the entire server.
  • Block I/O (blkio / io): restricts bandwidth and IOPS per device. You can assign proportional weight or set exact limits in bytes/s ops/s.
  • NetworkMechanisms exist to limit bandwidth and packets, although support is less mature and is usually combined with tc and QoS.
  • PIDs: mark the maximum number of processes/threads which can create a group, mitigating fork bombs or code errors that trigger threads.

The practical idea is very straightforward: you place your application in a cgroup and specify maximum CPU, memory, I/O, and PIDs . From there, the kernel takes care of enforcing the rules.

cgroups v1 vs cgroups v2

In production you'll find two flavors of cgroups, and it's important to be clear about which territory you're treading on:

  • cgroup v1Each controller has its own independent hierarchy. You have routes like /sys/fs/cgroup/cpu/..., /sys/fs/cgroup/memory/..., etc. It is flexible but complicated to manage on a large scale.Many classic distributions and platforms still use it or support it for compatibility.
  • cgroup v2: groups everything under one unified hierarchy en /sys/fs/cgroupThe limits are configured via files such as cpu.max, memory.max, io.max and the like. It simplifies management and improves fine-tuning of resourcesModern distributions tend to use v2 by default, sometimes with hybrid mode.
  How to securely delete files so they cannot be recovered

En /proc/cgroups You can see which controllers are enabled, how many active cgroups there are, and whether you're in v1 or v2 (in v2 you'll see a unified hierarchy with ID 0). The command mount | grep cgroup It also tells you the type of file system (cgroup2fs implies v2, tmpfs (usually points to v1).

Manual creation of cgroups and typical limits

To fully understand what Docker does under the hood, it's worth creating cgroups manually. The general pattern is:

  • Create a cgroup directory.
  • Configure limit/priority files (CPU, memory, I/O, PIDs…).
  • Add PIDs to cgroup by typing in cgroup.procs o tasks (in v1).

For example, in cgroups v2, you could have something like this for CPU and memory:

mkdir /sys/fs/cgroup/test_app
echo "+cpu +memory +io +pids" > /sys/fs/cgroup/cgroup.subtree_control
echo "50000 100000" > /sys/fs/cgroup/test_app/cpu.max
echo "512M" > /sys/fs/cgroup/test_app/memory.max
echo $$ > /sys/fs/cgroup/test_app/cgroup.procs

With this, the current process is limited to 50% of a core and 512 MB of RAMIf you start something intensive (a dd to /dev/null, for example), you'll see that the system doesn't go haywire. In cgroups v1, the scheme is similar but with files like cpu.shares, cpu.cfs_quota_us, memory.limit_in_bytesetc., each in its own hierarchy.

How Docker, Kubernetes, and the kernel stack

Above namespaces and cgroups are several layers of software that pass the buck back and forth until they reach the kernel. Even if the tool name changes, the end result is always the same: kernel calls to create namespaces, configure cgroups, and mount file systems.

The typical container "stack" today looks a lot like this:

  • Orchestration layerKubernetes, Docker Swarm, Nomad… They handle the pod/service lifecycle, scheduling, scaling, and support microservices architectures.
  • high-level container runtime: Docker Engine, containerd, CRI-O. They receive the orchestrator's orders and turn them into concrete actions. (download images, create containers, manage snapshots, etc.).
  • Low-level OCI runtimeRunc, crun, and similar tools implement the OCI specification and are directly responsible for implementing it. they call clone(), unshare(), mount() and company to materialize the container.
  • Linux kernelIt provides namespaces, cgroups, OverlayFS, and all other primitives. This is where the real "magic" happens.

When you throw a docker runThe path is: CLI client → dockerd → containerd → runc → kernel calls. Along this path, namespaces are created, cgroups are assigned, the rootfs is mounted with OverlayFS plus volumes, and the network interfaces and bridge are connected. What you see as "a container" is, for the kernel, a handful of processes with certain flags and special paths.

Requirements and prior checks in the system

Before you start messing around with namespaces and cgroups on a server, it's a good idea to make sure that the hardware and kernel meet reasonable minimum requirements , especially if you plan to set up something similar to a production environment.

As a reference, at least 2 logical CPUs, 4 GB of RAM (better 8 GB or more) , about 20 GB of free disk space, and a kernel 3.10 or higher (ideally 4.x or 5.x to have all the flourishes of cgroups v2 and modern features) are usually recommended.

To verify that the kernel supports namespaces and cgroups, simply check the configuration file of the version in use:

grep -E "CONFIG_.*_NS" /boot/config-$(uname -r)
grep -E "CONFIG_CGROUP" /boot/config-$(uname -r)

Options like these should appear there CONFIG_NAMESPACES, CONFIG_PID_NS, CONFIG_NET_NS, CONFIG_CGROUPS, CONFIG_MEMCG, CONFIG_CGROUP_SCHED marked as =yWithout that, you won't have much of a game.

Another practical detail is whether the system allows create user namespaces without being rootThis is very useful for testing and some tools. It's controlled with sysctl. kernel.unprivileged_userns_cloneIf it's at 0, you'll have to activate it (or use your sweat everywhere).

Exploring cgroups and namespaces on "real" machines

A fairly straightforward way to internalize these concepts is to look at how the system itself and your containers use them . Some helpful hints:

  • ls /proc/self/ns/ It shows you the namespaces to which your current shell belongs (cgroup, ipc, mnt, net, pid, user, uts…).
  • systemd-cgtop It shows CPU, memory, and I/O usage by cgroup, very similar to top but grouped by services and containers.
  • En /sys/fs/cgroup You can locate the cgroups that Docker or Podman create for each container, usually using the container ID in the path. Inside, cgroup.procs List the associated PIDs.
  • /proc/<pid>/cgroup It tells you which cgroups a specific process is assigned to, perfect for tracking down a problematic app.
  Operating systems news: a complete and up-to-date overview

You'll see that containers are simply another branch of the cgroups hierarchy, with their CPU, memory, blkio, and other limits, just as you could create them manually . The beauty of Docker is that it does this automatically and with a user-friendly API.

Performance, NUMA, and affinity strategies

In high-load scenarios (large databases, intensive processing, streaming, etc.), simply "launching containers" isn't enough. How you allocate CPU and memory to each service significantly impacts performance.

With the cpuset driver, you can assign processes or services to specific sets of CPUs and NUMA nodes. It's as simple as:

mkdir /sys/fs/cgroup/cpuset/dedicated_cpus
echo "4-7" > /sys/fs/cgroup/cpuset/dedicated_cpus/cpuset.cpus
echo "0" > /sys/fs/cgroup/cpuset/dedicated_cpus/cpuset.mems
echo 1 > /sys/fs/cgroup/cpuset/dedicated_cpus/cpuset.cpu_exclusive

It allows you to reserve a group of cores for a specific service and prevent the rest of the system from interfering. This reduces noise, thread migrations, and latency problems that often arise when everyone is competing for the same CPUs.

At the memory level, combining numactl with cgroups (or directly using the memory controller's NUMA knobs when available) helps keep data on the physical node closest to the cores that use it. This is noticeable in latency-sensitive applications.

Monitoring and observability of cgroups and namespaces

Setting resource limits is fine, but If you don't measure what's happening, you could shoot yourself in the foot without even realizing it.cgroup v2 itself offers quite a few useful metrics in the logs *.stat, *.events y *.pressure.

Some practical examples:

  • cpu.stat It tells you how much CPU time the group has used, how many times it has been throttled, etc.
  • memory.current y memory.max They show current consumption and configured limit.
  • memory.events It records OOMs, soft limits reached and other scares.
  • io.stat accumulates reads/writes per device.

For a quick overview, systemd-cgtop It's very practical, and if you want something more industrial, tools like cAdvisor They export Docker/containerd cgroup metrics to Prometheus, allowing you to build complete dashboards in Grafana without too much effort, ideal for a advanced system monitor.

When debugging namespace problems, simple scripts that traverse /proc/<pid>/ns/ and compare the symbolic links help to to know which processes share exactly the same namespace, or to locate a “lost” network namespace that has become stuck.

Using systemd as a cgroup manager

In most modern distributions, systemd relies heavily on cgroups to manage services, slices, and scopes . When you define a unit with resource boundaries, what systemd actually does is create a cgroup for that service and configure its handlers.

A service declared with directives such as:

CPUQuota=50%
CPUWeight=500
MemoryMax=512M
IOReadBandwidthMax=/dev/sda 10M

This translates into cgroup limits on CPU, memory, and I/O for that unit's process. This allows for aligning "classic" service management with the container model, using the same underlying kernel subsystem. For more details about systemd and its requirements, see systemd changes.

For teams that mix traditional services with containers (very common), relying on systemd to set limits on critical daemons is an elegant way to prevent resources from being killed by the pods in the containerized environment.

In environments with multiple workloads, from databases to monitoring agents and custom applications, mastering namespaces and cgroups allows you to define strong isolation policies, adjust quality of service, and ensure that each component has its own well-controlled allocation of CPU and memory . Understanding how Docker, Kubernetes, and systemd leverage these kernel capabilities equips you to design more secure multi-tenant infrastructures, debug complex problems (from OOMs to I/O bottlenecks), and get the most out of your hardware without sacrificing stability.

systemd 259 support for musl
Related articles:
systemd 259: support for musl, security, and key changes