Skip to content
Codeloom
Linux

Linux Namespaces: The Building Blocks of Containers

Understand Linux namespaces and how they provide process isolation for containers, with hands-on examples using unshare.

·8 min read · By Codeloom
Intermediate 12 min read

What you'll learn

  • What Linux namespaces are and which types exist
  • How to create and inspect namespaces with unshare and nsenter
  • How containers use namespaces for process isolation

Prerequisites

  • Basic Linux command line
  • Familiarity with Linux processes and PIDs

What Are Namespaces?

Linux namespaces are a kernel feature that partitions system resources so that a set of processes sees one view of the system while another set sees a different view. They are the isolation mechanism that makes containers possible.

Without namespaces, every process on a Linux system shares the same view of the hostname, network interfaces, process IDs, mount points, and users. Namespaces break this shared view, giving each container its own isolated environment.

Together with cgroups (for resource limits) and union filesystems (for layered images), namespaces form the three pillars of Linux container technology.

Types of Namespaces

Linux provides eight types of namespaces, each isolating a different system resource:

NamespaceFlagIsolates
MountCLONE_NEWNSMount points and filesystems
UTSCLONE_NEWUTSHostname and domain name
IPCCLONE_NEWIPCSystem V IPC and POSIX message queues
PIDCLONE_NEWPIDProcess IDs
NetworkCLONE_NEWNETNetwork interfaces, routing, ports
UserCLONE_NEWUSERUser and group IDs
CgroupCLONE_NEWCGROUPCgroup root directory
TimeCLONE_NEWTIMESystem clocks (Linux 5.6+)

Viewing Namespaces

Every process on Linux belongs to a set of namespaces. You can see them in /proc:

# View namespaces of the current shell
ls -la /proc/$$/ns/
# lrwxrwxrwx 1 user user 0 Jul  2 10:00 cgroup -> cgroup:[4026531835]
# lrwxrwxrwx 1 user user 0 Jul  2 10:00 ipc -> ipc:[4026531839]
# lrwxrwxrwx 1 user user 0 Jul  2 10:00 mnt -> mnt:[4026531841]
# lrwxrwxrwx 1 user user 0 Jul  2 10:00 net -> net:[4026531840]
# lrwxrwxrwx 1 user user 0 Jul  2 10:00 pid -> pid:[4026531836]
# lrwxrwxrwx 1 user user 0 Jul  2 10:00 user -> user:[4026531837]
# lrwxrwxrwx 1 user user 0 Jul  2 10:00 uts -> uts:[4026531838]

# List all namespaces on the system
lsns

# List namespaces for a specific process
lsns -p 1234

The numbers in brackets (like 4026531835) are inode numbers that uniquely identify each namespace. Processes sharing the same inode number are in the same namespace.

Hands-On: UTS Namespace (Hostname Isolation)

The UTS namespace isolates the hostname. Let’s create a new one:

# Create a new UTS namespace and start a shell inside it
sudo unshare --uts /bin/bash

# Inside the new namespace, change the hostname
hostname container-host
hostname
# container-host

# Open a new terminal and check the host's real hostname
hostname
# your-real-hostname (unchanged!)

The hostname change is only visible inside the namespace.

Hands-On: PID Namespace

The PID namespace gives processes their own PID numbering, starting from 1:

# Create a new PID and mount namespace
sudo unshare --pid --mount-proc --fork /bin/bash

# Inside the namespace, PID 1 is our bash process
ps aux
# USER  PID %CPU %MEM    VSZ   RSS TTY  STAT START   TIME COMMAND
# root    1  0.0  0.0   9688  4836 pts/0 S  10:00   0:00 /bin/bash
# root    2  0.0  0.0  12136  3352 pts/0 R+ 10:00   0:00 ps aux

# The host's processes are invisible
echo $$
# 1

The --fork flag is required because the PID namespace only takes effect for child processes. --mount-proc remounts /proc so that ps shows the correct PID view.

Hands-On: Network Namespace

Network namespaces provide isolated network stacks, each with its own interfaces, routing tables, and port space:

# Create a named network namespace
sudo ip netns add mycontainer

# List network namespaces
ip netns list

# Run a command inside the namespace
sudo ip netns exec mycontainer ip addr
# Only a loopback interface exists, and it's DOWN

# Bring up the loopback
sudo ip netns exec mycontainer ip link set lo up

To connect a network namespace to the host, use a veth pair (virtual ethernet):

# Create a veth pair
sudo ip link add veth-host type veth peer name veth-container

# Move one end into the namespace
sudo ip link set veth-container netns mycontainer

# Configure IP addresses
sudo ip addr add 10.0.0.1/24 dev veth-host
sudo ip link set veth-host up

sudo ip netns exec mycontainer ip addr add 10.0.0.2/24 dev veth-container
sudo ip netns exec mycontainer ip link set veth-container up

# Test connectivity
sudo ip netns exec mycontainer ping 10.0.0.1
ping 10.0.0.2

This is exactly how Docker creates networking for containers — each container gets a veth pair connecting it to a bridge on the host.

Hands-On: Mount Namespace

Mount namespaces isolate the filesystem view. Changes to mounts in one namespace are invisible in another:

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

# Create a tmpfs mount that only exists in this namespace
mkdir -p /mnt/secret
mount -t tmpfs tmpfs /mnt/secret
echo "namespace-only data" > /mnt/secret/data.txt

# In another terminal, /mnt/secret is empty or doesn't exist
ls /mnt/secret/
# (empty or error)

Hands-On: User Namespace

User namespaces allow a process to have root privileges inside the namespace while being an unprivileged user on the host. This is the foundation of rootless containers:

# Create a user namespace (no sudo needed!)
unshare --user --map-root-user /bin/bash

# Inside the namespace, you appear to be root
whoami
# root
id
# uid=0(root) gid=0(root)

# But on the host, you're still your regular user
# This "root" cannot actually modify host resources

How Docker Uses Namespaces

When you run docker run -it ubuntu bash, Docker creates a process with all these namespaces:

# See the namespaces of a Docker container
docker inspect --format '{{.State.Pid}}' <container_id>
# Returns the PID on the host, e.g., 5678

ls -la /proc/5678/ns/
# Each namespace has a unique inode, different from the host

A Docker container typically uses:

  • PID namespace — Container sees only its own processes, PID 1 is the entrypoint
  • Network namespace — Container has its own eth0, IP address, and port space
  • Mount namespace — Container sees the image’s filesystem, not the host’s
  • UTS namespace — Container has its own hostname (the container ID)
  • IPC namespace — Isolated shared memory and message queues
  • User namespace — (Optional) Maps container root to unprivileged host user

Entering Existing Namespaces with nsenter

nsenter lets you enter the namespaces of a running process. This is invaluable for debugging containers:

# Enter all namespaces of a container process
sudo nsenter -t <PID> --all

# Enter just the network namespace
sudo nsenter -t <PID> --net

# Debug a container's network without exec
CONTAINER_PID=$(docker inspect --format '{{.State.Pid}}' mycontainer)
sudo nsenter -t $CONTAINER_PID --net ip addr
sudo nsenter -t $CONTAINER_PID --net ss -tlnp
sudo nsenter -t $CONTAINER_PID --net curl localhost:8080

Building a Minimal Container

Combining namespaces, we can build a rudimentary container from scratch:

# Create an isolated environment with multiple namespaces
sudo unshare --pid --mount --uts --ipc --net --fork /bin/bash

# Set the hostname
hostname my-container

# Mount a fresh proc
mount -t proc proc /proc

# Now we have:
# - Our own PID space (we are PID 1)
# - Our own hostname
# - Our own network stack (empty, no interfaces)
# - Our own mount table
# - Our own IPC resources

This is essentially what container runtimes like runc do, plus adding cgroups for resource limits and a root filesystem from an image.

Namespace Persistence

Namespaces normally disappear when the last process inside them exits. To keep a namespace alive without a running process, you can bind-mount it:

# Create a persistent network namespace
sudo ip netns add persistent-ns
# This creates /var/run/netns/persistent-ns as a bind mount

# Or manually bind-mount any namespace
sudo touch /var/run/ns/my-pid-ns
sudo mount --bind /proc/$$/ns/pid /var/run/ns/my-pid-ns

Summary

Linux namespaces are the isolation technology that makes containers possible. Each namespace type isolates a different system resource: PID for process IDs, network for network stacks, mount for filesystems, UTS for hostnames, user for UID/GID mappings, and more. You can create and explore namespaces with unshare and enter existing ones with nsenter. Understanding namespaces demystifies what Docker and Kubernetes are actually doing under the hood — they are not virtual machines, but clever combinations of kernel namespace and cgroup features.