Skip to content

Lab 09 โ€” Linux Namespaces: The Building Blocks of Containers

Field Details
Course SCIA-360 OS Security
Topic Sandboxing & Containerization
Chapter 8
Difficulty โญโญ Intermediate
Estimated Time 60โ€“75 minutes
Requires Docker, ubuntu:22.04 image

Overview

Linux namespaces are the foundational kernel technology behind every container runtime โ€” Docker, Podman, and containerd all rely on them. Each namespace type isolates a different aspect of the operating system, creating the illusion that a process has its own private view of the system.

In this lab you will use the unshare utility to manually create isolated namespaces one at a time, observing exactly what each namespace type isolates. By the end you will understand precisely why Docker containers behave as isolated systems โ€” because the kernel enforces each isolation boundary at the namespace level.

Namespace types covered:

Namespace Flag Isolates
UTS --uts Hostname and domain name
PID --pid Process ID number space
NET --net Network interfaces, routing, ports
MNT --mount Filesystem mount points
IPC --ipc Inter-process communication (semaphores, shared memory)
USER --user User and group ID mappings
CGROUP --cgroup cgroup root directory view
TIME --time System clock offsets

Privileged containers required

Several unshare operations require --privileged on the Docker container. Only run privileged containers in isolated lab environments โ€” never in production. The privilege is needed here so the container itself has sufficient capabilities to create sub-namespaces.


Part 1 โ€” Namespace Overview

Step 1.1 โ€” Inspect the namespace filesystem

Every Linux process has a directory at /proc/<pid>/ns/ containing symbolic links โ€” one per namespace โ€” that identify which namespace instance the process belongs to. The number in brackets (e.g., uts:[4026531838]) is the inode of the namespace; two processes sharing the same inode are in the same namespace.

docker run --rm --privileged ubuntu:22.04 bash -c "
apt-get update -qq && apt-get install -y -qq util-linux iproute2 procps 2>/dev/null
echo '=== Namespaces for this process ==='
ls -la /proc/self/ns/"

Expected output: A listing of symlinks including cgroup, ipc, mnt, net, pid, time, user, and uts, each with a unique numeric ID such as uts:[4026531838].

What to look for

Each symlink name corresponds to one namespace type. The number is an inode ID โ€” if two processes share the same inode for a given namespace type, they share that namespace and can see each other's resources of that type.

๐Ÿ“ธ Screenshot checkpoint 09a: Capture the full ls -la /proc/self/ns/ output showing all namespace symlinks and their inode IDs.


Part 2 โ€” UTS Namespace (Hostname Isolation)

The UTS namespace (Unix Time-Sharing) isolates the system hostname and NIS domain name. Changing the hostname inside a UTS namespace does not affect the host or any other namespace.

Step 2.1 โ€” Demonstrate hostname isolation

docker run --rm --privileged ubuntu:22.04 bash -c "
apt-get update -qq && apt-get install -y -qq util-linux 2>/dev/null
echo 'Before:' && hostname
unshare --uts bash -c 'hostname isolated-host && echo Inside: \$(hostname)'
echo 'After:' && hostname"

Expected output:

Before: <container-id>
Inside: isolated-host
After: <container-id>    โ† unchanged

The unshare --uts command creates a new UTS namespace for the child process. The child can set its own hostname without any effect on the parent process or the host system.

๐Ÿ“ธ Screenshot checkpoint 09b: Capture the output showing "Before", "Inside: isolated-host", and "After" with the original hostname restored.


Part 3 โ€” PID Namespace (Process ID Isolation)

The PID namespace creates an independent numbering space for process IDs. The first process inside a new PID namespace gets PID 1 โ€” regardless of its actual PID from the parent's perspective.

Step 3.1 โ€” Demonstrate PID remapping

docker run --rm --privileged ubuntu:22.04 bash -c "
apt-get update -qq && apt-get install -y -qq util-linux procps 2>/dev/null
echo 'Parent PID:' && echo \$\$
unshare --pid --fork bash -c 'echo Inside PID namespace: my PID is \$\$'"

Expected output:

Parent PID: 94          โ† or some value from the container's PID space
Inside PID namespace: my PID is 2   โ† low number, isolated space

๐Ÿ“ธ Screenshot checkpoint 09c: Capture the parent PID vs. the inside PID showing they are different numbers.


Step 3.2 โ€” Process visibility isolation

Inside a PID namespace with a private /proc mount, a process cannot see processes from the parent namespace โ€” they simply do not exist from its perspective.

docker run --rm --privileged ubuntu:22.04 bash -c "
apt-get update -qq && apt-get install -y -qq util-linux procps 2>/dev/null
echo 'Parent sees:' && ps aux | wc -l && echo 'processes'
unshare --pid --fork --mount-proc bash -c 'echo Inside sees: && ps aux | wc -l && echo processes && ps aux'"

Expected output:

Parent sees:
12                 โ† several processes visible
processes
Inside sees:
3                  โ† only bash + ps (+ header line)
processes
USER  PID  ...
root    1  bash
root    2  ps aux

The --mount-proc flag mounts a fresh /proc filesystem inside the new PID namespace, reflecting only the processes visible in that namespace.

๐Ÿ“ธ Screenshot checkpoint 09d: Capture both process counts โ€” the high number outside and the 2โ€“3 processes visible inside.


Part 4 โ€” Network Namespace (Network Stack Isolation)

The network namespace provides each isolated environment with its own network interfaces, routing tables, firewall rules, and port number space. A process in a new network namespace starts with only a loopback interface โ€” no external connectivity until explicitly configured.

Step 4.1 โ€” Demonstrate interface isolation

docker run --rm --privileged ubuntu:22.04 bash -c "
apt-get update -qq && apt-get install -y -qq util-linux iproute2 2>/dev/null
echo '=== Parent interfaces ==='
ip link show | grep -E '^[0-9]+:'
echo '=== Inside net namespace (only loopback) ==='
unshare --net bash -c 'ip link show | grep -E \"^[0-9]+:\"'
echo '=== Parent interfaces still intact ==='
ip link show | grep -c '^[0-9]'"

Expected output:

=== Parent interfaces ===
1: lo: ...
2: eth0: ...        โ† or similar

=== Inside net namespace (only loopback) ===
1: lo: ...          โ† only loopback โ€” no eth0

=== Parent interfaces still intact ===
2                   โ† parent still has both interfaces

๐Ÿ“ธ Screenshot checkpoint 09e: Capture the three sections showing parent interfaces, the isolated namespace with only lo, and confirmation that parent interfaces are untouched.


Part 5 โ€” Mount Namespace (Filesystem Isolation)

The mount namespace isolates the set of filesystem mount points visible to a process. Changes to mounts inside a namespace (mounting, unmounting) do not propagate to the parent namespace. This is how Docker gives each container its own root filesystem.

Step 5.1 โ€” Demonstrate mount isolation

docker run --rm --privileged ubuntu:22.04 bash -c "
apt-get update -qq && apt-get install -y -qq util-linux 2>/dev/null
echo 'Parent /tmp:' && ls /tmp
unshare --mount bash -c '
  mount -t tmpfs tmpfs /tmp
  echo namespace_secret > /tmp/secret.txt
  echo Inside /tmp: && ls /tmp
'
echo 'Parent /tmp (unchanged):' && ls /tmp"

Expected output:

Parent /tmp:
                        โ† empty or system files only

Inside /tmp:
secret.txt              โ† exists only inside the namespace

Parent /tmp (unchanged):
                        โ† secret.txt NOT here

The child process mounted a fresh tmpfs over /tmp within its private mount namespace. From the parent's perspective, /tmp was never touched.

๐Ÿ“ธ Screenshot checkpoint 09f: Capture all three sections โ€” parent before, inside with secret.txt, parent after with /tmp unchanged.


Part 6 โ€” All Namespaces Together (Docker)

Docker automatically creates all namespace types simultaneously when starting a container, giving it complete OS-level isolation. You can observe the unique namespace IDs assigned to any running container.

Step 6.1 โ€” Inspect a container's namespace IDs

docker run --rm ubuntu:22.04 bash -c "
echo '=== This container has its own namespace IDs ==='
ls -la /proc/self/ns/ | awk '{print \$11, \$12, \$13}' | grep -v '^$'
echo 'Each ID is unique - Docker created new namespaces for this container'"

๐Ÿ“ธ Screenshot checkpoint 09g-part1: Capture the namespace listing for this container.


Step 6.2 โ€” Two containers: separate network namespaces

Each container gets its own network namespace, which means its own IP address space. Observe that two containers on the same Docker network receive different IPs, proving they inhabit different network namespaces.

docker network create ns-demo

docker run -d --name ns-a --network ns-demo ubuntu:22.04 sleep 60
docker run -d --name ns-b --network ns-demo ubuntu:22.04 sleep 60

IP_A=$(docker inspect ns-a --format '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}')
IP_B=$(docker inspect ns-b --format '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}')

echo "Container A IP: $IP_A"
echo "Container B IP: $IP_B"

docker stop ns-a ns-b && docker rm ns-a ns-b
docker network rm ns-demo

Expected output:

Container A IP: 172.18.0.2
Container B IP: 172.18.0.3    โ† different IPs โ€” separate network namespaces

๐Ÿ“ธ Screenshot checkpoint 09g: Capture both container IPs confirming they are in separate network namespaces.


Cleanup

docker system prune -f

Assessment

Screenshot Checklist

ID Description Points
09a /proc/self/ns/ listing showing all namespace symlinks with inode IDs 6
09b UTS hostname isolation โ€” "Inside: isolated-host" vs. unchanged outside hostname 6
09c PID isolation โ€” parent high PID vs. inside low PID 6
09d Process count isolation โ€” many outside vs. 2โ€“3 inside 6
09e Network namespace โ€” eth0 outside, only lo inside 6
09f Mount namespace โ€” secret.txt inside, /tmp clean outside 6
09g Two containers with different IP addresses 4
Screenshot subtotal 40

Grading Rubric

Component Points
Screenshots (checklist above) 40
Namespace type comparison table (complete all 8 types with: flag, what it isolates, real-world Docker use) 20
Reflection questions (4 ร— 10 pts each) 40
Total 100

Namespace Type Table (complete and submit)

Fill in this table as part of your lab submission:

Namespace unshare Flag What It Isolates Docker Use Case
UTS --uts
PID --pid
NET --net
MNT --mount
IPC --ipc
USER --user
CGROUP --cgroup
TIME --time

Reflection Questions

Answer each question in 150โ€“200 words.

  1. Namespace completeness: List the six main Linux namespace types and what each isolates. Why must a container runtime use all of them together (not just one) to achieve proper container-level isolation? Give a concrete example of what could go wrong if only the network namespace were used without the PID namespace.

  2. PID 1 and init: In the PID namespace demo, the shell inside the namespace received PID 1. What is special about PID 1 in Linux (the init process)? If PID 1 inside a container crashes or exits, what happens to all other processes running inside that container? What does this mean for container design โ€” specifically, why should long-running containers use a proper init process?

  3. Overlay filesystems: The mount namespace demo showed that creating a file in /tmp inside a namespace did not appear outside. How does Docker extend this concept โ€” using mount namespaces together with overlay filesystems (overlayfs) โ€” to give each container its own complete root filesystem that is copy-on-write layered on top of a shared image?

  4. Namespaces vs. cgroups: Namespaces provide isolation (visibility boundaries) but do not enforce resource limits. What Linux mechanism works alongside namespaces to prevent one container from consuming all CPU or RAM on the host? Explain how these two technologies โ€” namespaces and cgroups โ€” combine to create the complete container security and resource model used by Docker.