Back to Containers & Runtime Environments Mastery Series

Part 2: Linux Namespaces — The Foundation of Isolation

May 14, 2026 Wasil Zafar 30 min read

In Part 1, we learned that containers are isolated processes sharing the host kernel. But how does the kernel create that isolation? The answer is namespaces — the Linux kernel feature that controls what a process can see.

Table of Contents

  1. What Are Namespaces?
  2. PID Namespace
  3. Network Namespace
  4. Mount Namespace
  5. UTS Namespace
  6. IPC Namespace
  7. User Namespace
  8. Combining Namespaces
  9. Exercises
  10. Conclusion & Next Steps

What Are Namespaces?

A Linux namespace wraps a global system resource in an abstraction that makes it appear to processes within the namespace that they have their own isolated instance of the resource. Changes to the global resource are visible to processes in the same namespace but invisible to processes in other namespaces.

Think of namespaces as one-way mirrors. A process inside a namespace looks out and sees only what the kernel wants it to see. The host (or a process in the parent namespace) can see everything — including all the processes inside all the namespaces. But from inside, the world looks small, private, and self-contained.

The One-Way Mirror Analogy: Imagine a police interrogation room. The suspect (container process) sees only the room — their entire world. But the detectives (host) can see through the one-way mirror into every interrogation room simultaneously. Namespaces work the same way: they restrict what a process can perceive, not what actually exists on the system.

Namespaces do not provide resource limits — that is the job of cgroups (Part 3). Namespaces provide resource visibility control. A process in a PID namespace cannot see other processes, but it is not limited in how many processes it can create. A process in a network namespace cannot see the host's network interfaces, but it is not limited in how much bandwidth it can consume.

The distinction is critical: namespaces control what you can see; cgroups control what you can use. Together they create the complete container isolation model.

The Six Namespace Types

Linux provides six namespace types (seven if you count the newer cgroup namespace, but we focus on the original six that form the core of container isolation):

Namespace Flag Isolates Kernel Version
PID CLONE_NEWPID Process IDs 2.6.24 (2008)
Network CLONE_NEWNET Network devices, ports, routing 2.6.29 (2009)
Mount CLONE_NEWNS Filesystem mount points 2.4.19 (2002)
UTS CLONE_NEWUTS Hostname and domain name 2.6.19 (2006)
IPC CLONE_NEWIPC System V IPC, POSIX message queues 2.6.19 (2006)
User CLONE_NEWUSER User and group IDs 3.8 (2013)

Every process on a Linux system belongs to exactly one instance of each namespace type. By default, all processes share the same set of namespaces (the "initial" or "root" namespaces). When you create a container, the runtime creates new namespaces and places the container's processes into them.

# View the namespaces of a process (PID 1 = init/systemd)
ls -la /proc/1/ns/

# Output:
# lrwxrwxrwx 1 root root 0 May 14 10:00 cgroup -> 'cgroup:[4026531835]'
# lrwxrwxrwx 1 root root 0 May 14 10:00 ipc -> 'ipc:[4026531839]'
# lrwxrwxrwx 1 root root 0 May 14 10:00 mnt -> 'mnt:[4026531840]'
# lrwxrwxrwx 1 root root 0 May 14 10:00 net -> 'net:[4026531992]'
# lrwxrwxrwx 1 root root 0 May 14 10:00 pid -> 'pid:[4026531836]'
# lrwxrwxrwx 1 root root 0 May 14 10:00 user -> 'user:[4026531837]'
# lrwxrwxrwx 1 root root 0 May 14 10:00 uts -> 'uts:[4026531838]'

# The numbers in brackets are inode numbers — unique identifiers for each namespace instance
# Two processes with the same inode share the same namespace

PID Namespace — Process Tree Isolation

The PID namespace is perhaps the most intuitive. It gives a process its own view of the process ID number space. The first process in a new PID namespace gets PID 1 — just like the init process on the host. It cannot see processes outside its namespace, and processes outside cannot be signalled from within.

This is why every Docker container has a PID 1 process. It is not a coincidence or a convention — it is a direct consequence of PID namespace isolation. The container's entrypoint process becomes PID 1 in its namespace, regardless of what PID it has on the host.

PID Namespace — Host vs Container View
flowchart TD
    subgraph HOST["Host PID Namespace"]
        P1["PID 1: systemd"]
        P2["PID 452: sshd"]
        P3["PID 1089: containerd"]
        P4["PID 2341: nginx (container)"]
        P5["PID 2342: nginx worker"]
        P6["PID 2343: nginx worker"]
    end
    subgraph CONTAINER["Container PID Namespace"]
        C1["PID 1: nginx master"]
        C2["PID 2: nginx worker"]
        C3["PID 3: nginx worker"]
    end
    P4 -.->|"same process"| C1
    P5 -.->|"same process"| C2
    P6 -.->|"same process"| C3
    style HOST fill:#132440,stroke:#3B9797,color:#fff
    style CONTAINER fill:#16476A,stroke:#3B9797,color:#fff
    style P1 fill:#3B9797,stroke:#132440,color:#fff
    style P2 fill:#3B9797,stroke:#132440,color:#fff
    style P3 fill:#3B9797,stroke:#132440,color:#fff
    style P4 fill:#BF092F,stroke:#132440,color:#fff
    style P5 fill:#BF092F,stroke:#132440,color:#fff
    style P6 fill:#BF092F,stroke:#132440,color:#fff
    style C1 fill:#BF092F,stroke:#132440,color:#fff
    style C2 fill:#BF092F,stroke:#132440,color:#fff
    style C3 fill:#BF092F,stroke:#132440,color:#fff
                            

Notice the duality: the nginx master process is simultaneously PID 2341 on the host and PID 1 inside the container. Both are correct — they are just different views of the same process, mediated by the PID namespace boundary.

Hands-On: Creating a PID Namespace

You do not need Docker to experiment with namespaces. The unshare command lets you create new namespaces directly. Let us create a PID namespace and observe the isolation:

# Create a new PID namespace with its own /proc filesystem
# --pid: create new PID namespace
# --fork: fork before executing the command (required for PID namespaces)
# --mount-proc: mount a new /proc so 'ps' works correctly
sudo unshare --pid --fork --mount-proc bash

# Inside the new namespace, list all processes
ps aux
# Output:
# USER  PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
# root    1  0.0  0.0   8312  5264 pts/0    S    10:00   0:00 bash
# root    2  0.0  0.0  10072  3456 pts/0    R+   10:00   0:00 ps aux

# Only TWO processes visible! Bash is PID 1.
# The hundreds of host processes are completely invisible.

# Try to signal a host process (it will fail)
kill -0 452
# Output: bash: kill: (452) - No such process

# Exit the namespace
exit
PID 1 Responsibilities: The process with PID 1 in a namespace has special responsibilities. It becomes the parent of orphaned processes (zombie reaping) and receives signals differently. If PID 1 exits, the entire namespace is destroyed and all processes within it are killed. This is why Docker containers exit when their main process stops — PID 1 death means namespace death.
Experiment

The Zombie Reaping Problem

In traditional Linux, PID 1 (systemd/init) reaps zombie processes — children whose parents died before calling wait(). In a container, YOUR process is PID 1. If it spawns children and does not properly handle SIGCHLD, zombies accumulate. This is why tools like tini or dumb-init exist — they act as a proper init system inside containers, handling signal forwarding and zombie reaping so your application does not have to.

Zombie Processes Signal Handling tini

PID namespaces can also be nested. A container can create another PID namespace inside itself (this is how Docker-in-Docker works). Each level adds another layer of isolation, with the parent able to see the children but not vice versa.

Network Namespace — Virtual Network Stacks

A network namespace provides a process with its own complete network stack: network interfaces, IP addresses, routing tables, firewall rules, port numbers, and socket listings. A process in a network namespace cannot see or interact with network interfaces in other namespaces.

This is how containers get their own IP addresses and can all bind to port 80 without conflicts. Each container has its own network namespace with its own port space — port 80 in container A is completely separate from port 80 in container B.

The isolation is comprehensive. Each network namespace has:

Resource Per-Namespace Instance Example
Network interfaces Own set of NICs (virtual or physical) eth0, lo
IP addresses Independent address assignment 172.17.0.2/16
Routing table Own routing decisions default via 172.17.0.1
Firewall rules Independent iptables/nftables Container-specific rules
Port bindings Full 0–65535 port range Multiple containers on :80
Socket listings /proc/net/* isolated Only own connections visible

Hands-On: Creating Network Namespaces

The ip netns commands provide the most direct way to work with network namespaces. Let us create two namespaces and connect them with a virtual ethernet (veth) pair — this is exactly how Docker's bridge networking works:

# Create two network namespaces
sudo ip netns add container1
sudo ip netns add container2

# List all network namespaces
ip netns list
# Output:
# container2
# container1

# Run a command inside a namespace — check interfaces
sudo ip netns exec container1 ip addr
# Output: Only loopback (lo) exists, and it is DOWN
# 1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN
#     link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00

# Bring up loopback
sudo ip netns exec container1 ip link set lo up

# Create a virtual ethernet pair (like a virtual cable)
sudo ip link add veth0 type veth peer name veth1

# Move each end into a different namespace
sudo ip link set veth0 netns container1
sudo ip link set veth1 netns container2

# Assign IP addresses
sudo ip netns exec container1 ip addr add 10.0.0.1/24 dev veth0
sudo ip netns exec container2 ip addr add 10.0.0.2/24 dev veth1

# Bring up the interfaces
sudo ip netns exec container1 ip link set veth0 up
sudo ip netns exec container2 ip link set veth1 up

# Test connectivity — ping from container1 to container2
sudo ip netns exec container1 ping -c 3 10.0.0.2
# Output: 3 packets transmitted, 3 received, 0% packet loss

# Clean up
sudo ip netns delete container1
sudo ip netns delete container2
How Docker Networking Actually Works: When you run docker run, Docker creates a network namespace for the container, creates a veth pair, places one end in the container namespace (as eth0) and the other end on the docker0 bridge on the host. It then adds iptables rules for port mapping and NAT. The entire Docker bridge networking model is built on exactly the primitives shown above.
Deep Dive

Port Mapping Under the Hood

When you run docker run -p 8080:80 nginx, Docker does not magically make port 80 appear on your host's port 8080. Instead, it creates an iptables DNAT rule: traffic arriving at host:8080 is rewritten to be destined for the container's IP on port 80, then routed through the veth pair into the container's network namespace. This is why iptables -t nat -L on a Docker host shows many dynamically-created rules — one per published port.

iptables DNAT Port Mapping

Mount Namespace — Isolated Filesystem Views

The mount namespace was the first namespace type added to Linux (kernel 2.4.19, 2002) — which is why its clone flag is simply CLONE_NEWNS ("new namespace") without a more specific name. It provides a process with its own view of the filesystem mount tree.

When a process is placed in a new mount namespace, it starts with a copy of the parent's mount tree. After that, any mount or unmount operations within the namespace are invisible to processes outside it, and vice versa. This is how containers get their own root filesystem — a fresh mount tree that points to the container image layers.

Mount namespaces are the mechanism behind several critical container features:

  • Container root filesystem — The image's layers are mounted as the container's /
  • Volume mounts — Host directories bind-mounted into the container's mount tree
  • tmpfs mounts — In-memory filesystems for secrets and temp data
  • /proc and /sys isolation — Container-specific views of kernel interfaces

Hands-On: Isolated Mounts

# Create a new mount namespace
sudo unshare --mount bash

# Create a temporary directory and mount a tmpfs there
mkdir -p /tmp/secret-mount
mount -t tmpfs tmpfs /tmp/secret-mount

# Write something into it
echo "namespace-secret-data" > /tmp/secret-mount/secret.txt
cat /tmp/secret-mount/secret.txt
# Output: namespace-secret-data

# In ANOTHER terminal (host), check if the mount is visible:
mount | grep secret-mount
# Output: (nothing — the mount is invisible outside the namespace)

ls /tmp/secret-mount/
# Output: (empty — the directory exists but the tmpfs is not mounted here)

# Exit the namespace — the tmpfs and its data are destroyed
exit

This is powerful: you can mount filesystems, bind directories, and overlay layers inside a namespace, and none of it leaks to the host. When the namespace is destroyed, all its private mounts are automatically cleaned up.

Mount Propagation: Linux supports different mount propagation modes — shared, slave, private, and unbindable. These control whether mount events propagate between namespaces. Docker uses private propagation by default (mounts do not propagate), but you can override this with --mount type=bind,source=/src,target=/dst,bind-propagation=shared for specific use cases like mounting storage that needs to be visible across namespace boundaries.

UTS Namespace — Hostname Isolation

UTS stands for "UNIX Time-sharing System" — a historical name from the utsname structure that holds the system's hostname and domain name. A UTS namespace gives a process its own hostname and NIS domain name, independent of the host.

This might seem trivial compared to PID or network isolation, but it matters for applications that use the hostname for identification, logging, or cluster membership. Without UTS isolation, every container would report the host's hostname, making log analysis and service discovery confusing.

# Check current hostname
hostname
# Output: my-laptop

# Create a new UTS namespace
sudo unshare --uts bash

# Change the hostname inside the namespace
hostname container-web-01
hostname
# Output: container-web-01

# In ANOTHER terminal (on the host), check:
hostname
# Output: my-laptop (unchanged!)

# The hostname change is completely isolated to the namespace
exit

When you run docker run --hostname myapp nginx, Docker creates a UTS namespace and sets the hostname within it. The container's /etc/hostname reflects this value, and tools like hostname, logging frameworks, and application configuration all see the isolated name.

Real-World Impact

Hostname in Kubernetes Pods

In Kubernetes, each Pod gets a UTS namespace with its hostname set to the Pod name (e.g., web-frontend-7d8f9b4c5-x2kn4). This is critical for StatefulSets where each Pod needs a stable, unique identity (e.g., mysql-0, mysql-1, mysql-2) for clustering and replication. The UTS namespace makes this possible without conflicting with the node's actual hostname.

Kubernetes StatefulSets Pod Identity

IPC Namespace — Inter-Process Communication Isolation

The IPC namespace isolates System V IPC objects (shared memory segments, semaphores, message queues) and POSIX message queues. Without IPC namespace isolation, a process in one container could potentially read shared memory segments created by a process in another container — a serious security and correctness issue.

System V IPC uses numeric keys to identify shared resources. If two containers both create a shared memory segment with key 0x1234, they need to get independent segments — not accidentally share one. The IPC namespace ensures this.

The isolated IPC mechanisms include:

  • Shared memory segmentsshmget(), shmat(), shmdt()
  • Semaphore setssemget(), semop()
  • Message queuesmsgget(), msgsnd(), msgrcv()
  • POSIX message queuesmq_open(), mounted at /dev/mqueue
# List System V IPC resources on the host
ipcs
# Output shows shared memory segments, semaphores, message queues

# Create a new IPC namespace
sudo unshare --ipc bash

# Check IPC resources inside the namespace
ipcs
# Output: empty! No shared memory, no semaphores, no message queues.
# ------ Message Queues --------
# key        msqid      owner      perms      used-bytes   messages
#
# ------ Shared Memory Segments --------
# key        shmid      owner      perms      bytes      nattch     status
#
# ------ Semaphore Arrays --------
# key        semid      owner      perms      nsems

# Create a shared memory segment inside the namespace
ipcmk -M 1024
# Output: Shared memory id: 0

# This segment is invisible to the host and other namespaces
exit
Security Implications: Without IPC namespace isolation, a malicious container could attach to shared memory segments created by other containers or even host processes. This could allow data theft (reading sensitive data from shared memory), data corruption (writing to another process's shared memory), or denial of service (consuming all IPC resource slots). The IPC namespace is a critical security boundary.

User Namespace — UID/GID Mapping

The user namespace is the most security-critical namespace type — and the most recently completed (kernel 3.8, 2013). It maps user and group IDs between the namespace and the host. A process can be root (UID 0) inside a user namespace while being an unprivileged user (e.g., UID 100000) on the host.

This is the key to rootless containers. Without user namespaces, a process running as root inside a container is also root on the host — if it escapes the container (via a kernel vulnerability, for example), it has full host privileges. With user namespaces, "root" inside the container maps to an unprivileged UID outside, limiting the blast radius of any escape.

User Namespace UID Mapping
flowchart LR
    subgraph CONTAINER["Inside Container (User Namespace)"]
        U0["UID 0 (root)"]
        U1["UID 1 (daemon)"]
        U33["UID 33 (www-data)"]
    end
    subgraph HOST["Host System"]
        H100000["UID 100000 (unprivileged)"]
        H100001["UID 100001 (unprivileged)"]
        H100033["UID 100033 (unprivileged)"]
    end
    U0 -->|"maps to"| H100000
    U1 -->|"maps to"| H100001
    U33 -->|"maps to"| H100033
    style CONTAINER fill:#16476A,stroke:#3B9797,color:#fff
    style HOST fill:#132440,stroke:#3B9797,color:#fff
    style U0 fill:#BF092F,stroke:#132440,color:#fff
    style U1 fill:#3B9797,stroke:#132440,color:#fff
    style U33 fill:#3B9797,stroke:#132440,color:#fff
    style H100000 fill:#3B9797,stroke:#132440,color:#fff
    style H100001 fill:#3B9797,stroke:#132440,color:#fff
    style H100033 fill:#3B9797,stroke:#132440,color:#fff
                            

UID/GID Mapping in Practice

The mapping is defined in /proc/[pid]/uid_map and /proc/[pid]/gid_map. Each line defines a range mapping:

# Format: [inside_start] [outside_start] [count]
# Map UID 0-65535 inside the namespace to UID 100000-165535 on the host
echo "0 100000 65536" > /proc/$$/uid_map

# With Docker, enable user namespace remapping in /etc/docker/daemon.json:
# {
#   "userns-remap": "default"
# }

# Check the mapping of a running container
cat /proc/$(docker inspect --format '{{.State.Pid}}' mycontainer)/uid_map
# Output: 0     100000      65536

# Verify: files created as "root" inside container are owned by 100000 on host
docker run --rm -v /tmp/test:/mnt alpine touch /mnt/testfile
ls -la /tmp/test/testfile
# Without userns-remap: -rw-r--r-- 1 root root ...
# With userns-remap:    -rw-r--r-- 1 100000 100000 ...
Why Rootless Matters: In 2019, a critical vulnerability (CVE-2019-5736) allowed container escape through the runc binary. An attacker with root inside a container could overwrite the host's runc binary and execute arbitrary commands on the host as root. With user namespace remapping, "root" inside the container is UID 100000 on the host — it cannot overwrite system binaries even if it escapes the container boundary. User namespaces are the last line of defence.
Experiment

Rootless Docker in Action

Docker supports running the entire daemon without root privileges using user namespaces. Install rootless Docker with dockerd-rootless-setuptool.sh install, and the daemon runs as your regular user. All containers run with remapped UIDs. The trade-off: some features (like binding to privileged ports below 1024) require workarounds. But for development and CI/CD environments, rootless Docker dramatically reduces the attack surface.

Rootless Docker Security CVE-2019-5736

Combining Namespaces — How Docker Uses All Six

A container is not just one namespace — it is the combination of all six namespace types working together. Each namespace handles one dimension of isolation, and together they create the complete container illusion:

Container = All Namespaces Combined
flowchart TD
    PROC["Container Process"] --> PID["PID Namespace
Own process tree"] PROC --> NET["Network Namespace
Own IP, ports, routes"] PROC --> MNT["Mount Namespace
Own filesystem"] PROC --> UTS["UTS Namespace
Own hostname"] PROC --> IPC["IPC Namespace
Own shared memory"] PROC --> USR["User Namespace
Own UID mapping"] PID --> ISO["Complete Isolation Illusion"] NET --> ISO MNT --> ISO UTS --> ISO IPC --> ISO USR --> ISO style PROC fill:#BF092F,stroke:#132440,color:#fff style PID fill:#3B9797,stroke:#132440,color:#fff style NET fill:#3B9797,stroke:#132440,color:#fff style MNT fill:#3B9797,stroke:#132440,color:#fff style UTS fill:#3B9797,stroke:#132440,color:#fff style IPC fill:#3B9797,stroke:#132440,color:#fff style USR fill:#3B9797,stroke:#132440,color:#fff style ISO fill:#132440,stroke:#BF092F,color:#fff

When you execute docker run, the container runtime (runc) makes a series of system calls to create all the namespaces, configure them, and then exec the container's entrypoint process within them. The equivalent low-level operation looks like:

# What "docker run" does under the hood (simplified)
# 1. Create all namespaces with clone() or unshare()
sudo unshare --pid --net --mount --uts --ipc --user --fork bash

# 2. Set the hostname (UTS)
hostname my-container

# 3. Mount the container filesystem (Mount)
mount --bind /var/lib/docker/overlay2/merged /new-root
pivot_root /new-root /new-root/.old-root
umount /.old-root

# 4. Configure networking (Network)
# (container runtime creates veth pair and attaches to bridge)

# 5. Set up UID mapping (User)
echo "0 100000 65536" > /proc/$$/uid_map

# 6. Mount /proc for the PID namespace (PID)
mount -t proc proc /proc

# 7. Apply cgroup limits (not a namespace — covered in Part 3)

# 8. exec the entrypoint
exec nginx -g "daemon off;"

The runtime also selectively shares namespaces in some configurations. For example, in Kubernetes, containers within the same Pod share the network namespace (so they can communicate over localhost) and the IPC namespace (so they can use shared memory), but each has its own PID, mount, and UTS namespaces.

Kubernetes Pod = Shared Namespaces: A Kubernetes Pod is literally defined as a group of containers sharing network and IPC namespaces. The "pause" container creates these shared namespaces, and application containers join them. This is why containers in a Pod can reach each other on localhost and use shared memory for fast IPC — they are in the same network and IPC namespaces.

You can inspect which namespaces a Docker container uses with:

# Get the container's PID on the host
CONTAINER_PID=$(docker inspect --format '{{.State.Pid}}' my-container)

# List all namespace references
ls -la /proc/$CONTAINER_PID/ns/
# Output:
# lrwxrwxrwx 1 root root 0 ... cgroup -> 'cgroup:[4026532516]'
# lrwxrwxrwx 1 root root 0 ... ipc -> 'ipc:[4026532449]'
# lrwxrwxrwx 1 root root 0 ... mnt -> 'mnt:[4026532447]'
# lrwxrwxrwx 1 root root 0 ... net -> 'net:[4026532452]'
# lrwxrwxrwx 1 root root 0 ... pid -> 'pid:[4026532450]'
# lrwxrwxrwx 1 root root 0 ... user -> 'user:[4026531837]'
# lrwxrwxrwx 1 root root 0 ... uts -> 'uts:[4026532448]'

# Compare with host PID 1 — different inode numbers = different namespaces
ls -la /proc/1/ns/

# You can ENTER a container's namespaces from the host with nsenter
sudo nsenter --target $CONTAINER_PID --mount --uts --ipc --net --pid bash
# Now you are "inside" the container, seeing its filesystem, processes, network

Exercises

  1. PID Namespace Exploration — Use unshare --pid --fork --mount-proc bash to create a PID namespace. Inside, start a background process with sleep 300 &. In another terminal, find that sleep process on the host with ps aux | grep sleep. What PID does it have on the host? What PID does it have inside the namespace?
  2. Network Namespace Communication — Create two network namespaces and connect them with a veth pair (as shown in this article). Then extend the exercise: create a third namespace and set up routing so all three can communicate. Hint: you will need a bridge or a namespace acting as a router.
  3. Docker Namespace Inspection — Run docker run -d --name ns-test nginx:alpine. Use docker inspect to get its PID, then compare /proc/1/ns/ with /proc/[container-pid]/ns/. Which namespaces are different? Are any shared with the host?
  4. Namespace Escaping (Thought Experiment) — If a container process somehow gained access to the host's /proc/1/ns/net file and called setns() on it, what would happen? Why does this demonstrate that namespace isolation depends on preventing access to /proc on the host?

Conclusion & Next Steps

Namespaces are the first pillar of container isolation. They control visibility — what a process can see of the system around it:

  • PID namespace — isolates the process tree; container sees only its own processes
  • Network namespace — isolates network interfaces, IPs, ports, and routing
  • Mount namespace — isolates the filesystem mount tree
  • UTS namespace — isolates hostname and domain name
  • IPC namespace — isolates shared memory, semaphores, and message queues
  • User namespace — maps UIDs/GIDs for rootless security

But isolation is only half the story. A process that cannot see other processes can still consume all the CPU, exhaust all memory, or saturate all disk I/O — crashing the entire host. We need resource limits.

Next in the Series

In Part 3: Control Groups — Resource Management, we will explore cgroups — the kernel mechanism that puts hard limits on CPU, memory, I/O, and process count. While namespaces control what a process can see, cgroups control what it can use.