Understanding Linux Namespaces with unshare and nsenter
Explore Linux namespaces (PID, net, mount, user) with unshare and nsenter to demystify container isolation, with AI help acting as a fast junior pair.
- #linux
- #namespaces
- #containers
- #isolation
For years I treated containers as a black box. I ran docker run, things worked, and when they didn’t I shrugged and added another flag I half understood. The moment that changed was when I stopped reading container documentation and started reading kernel documentation. Containers are not magic. They are a thin layer of tooling over two kernel features: namespaces and cgroups. Once you can build a “container” by hand with unshare and inspect it with nsenter, the whole ecosystem stops being intimidating. This post is the walkthrough I wish I’d had: real commands, run on a real host, with nothing hidden.
What a Namespace Actually Is
A namespace is a kernel mechanism that gives a process its own isolated view of a particular global resource. There are several types: PID, network (net), mount (mnt), UTS (hostname), IPC, user, cgroup, and time. A process inside a PID namespace sees its own process tree starting at PID 1. A process in a net namespace sees its own interfaces, routing table, and firewall rules. None of this involves a virtual machine. It is the same kernel, the same scheduler, just partitioned views.
You can list the namespaces currently active on a host:
lsns
Every process exposes its namespace memberships as symlinks under /proc:
ls -l /proc/self/ns
ls -l /proc/$$/ns
Each link points to something like net:[4026531840]. That number is the inode of the namespace. Two processes sharing the same inode share the same namespace. This is the single most clarifying fact in the whole topic: “being in the same network” is literally “pointing at the same inode.”
Pro Tip: Run lsns -t net to filter to just network namespaces and instantly see which processes are isolated and which share the host’s stack.
Building a PID Namespace by Hand
Let’s create a process tree that thinks it is alone. The unshare command creates new namespaces and then runs a program inside them.
sudo unshare --pid --fork --mount-proc bash
Three flags matter here. --pid creates a new PID namespace. --fork is required because the process that creates a PID namespace does not itself enter it as PID 1; its first child does, so unshare must fork. --mount-proc remounts /proc so that tools like ps reflect the new tree rather than the host’s. Inside that shell:
ps aux
echo $$
You will see a tiny process list and your shell reporting PID 1. Kill PID 1 in this namespace and the whole namespace tears down, exactly as killing PID 1 does in a container.
Isolating the Network
A fresh network namespace starts with nothing but a loopback interface that is down:
sudo unshare --net bash
ip link
ip addr
Empty. No eth0, no route to anywhere. This is the blank canvas every container starts from. To make it useful you connect it to the host with a virtual ethernet (veth) pair, which behaves like a cable with a plug on each end. Do this from the host side, targeting the namespace’s process by PID:
# In the namespace shell, find its PID from the host with: lsns -t net
# Then, from the host:
sudo ip link add veth0 type veth peer name veth1
sudo ip link set veth1 netns <pid>
sudo ip addr add 10.200.1.1/24 dev veth0
sudo ip link set veth0 up
Then inside the namespace, bring up its end:
ip addr add 10.200.1.2/24 dev veth1
ip link set veth1 up
ip link set lo up
ping 10.200.1.1
That ping succeeding is the entire networking model of Docker bridge mode in miniature. Add NAT with iptables on the host and you have outbound internet. Nothing else is involved.
Entering an Existing Namespace with nsenter
unshare creates new namespaces. nsenter joins existing ones, which is how you debug a running container without its tooling. Given a target PID you can enter any subset of its namespaces:
# Enter the network namespace of process 12345
sudo nsenter -t 12345 -n ip addr
# Enter mount + PID + net together and get a shell
sudo nsenter -t 12345 -m -p -n bash
This is invaluable when a container ships a stripped image with no ip, ss, or curl. You stay in your host’s mount namespace, keep your own binaries, but jump into the target’s network view. I lean on AI a lot when constructing these flag combinations, but treat the suggestion the way you’d treat one from a fast junior engineer: quick, usually right, occasionally confidently wrong about which flags compose safely. A wrong -m on the host can leave you very confused.
User Namespaces and Rootless Isolation
User namespaces let an unprivileged user become root inside the namespace while remaining unprivileged outside it. This is the foundation of rootless containers.
unshare --user --map-root-user bash
id
No sudo required, yet id reports uid=0(root). Outside, the kernel still maps you to your real, restricted UID, so you cannot touch anything you couldn’t before. Combine it with other namespaces to build a sandbox a normal user can construct:
unshare --user --map-root-user --pid --fork --mount --mount-proc bash
This is genuinely safer experimentation territory, but “safer” is not “safe.” Don’t get cavalier and run this kind of throwaway tinkering on a box that holds anything sensitive.
Mount Namespaces and Propagation
Mount namespaces give a process its own filesystem mount table. This is what lets a container have its own / without affecting the host.
sudo unshare --mount bash
mount --bind /tmp /mnt
ls /mnt
That bind mount exists only inside this namespace. The subtle part is propagation. By default many distros mount / as shared, so new mounts can leak between namespaces. To get true isolation, container runtimes mark the tree private:
mount --make-rprivate /
After that, mounts you create stay contained and host changes don’t bleed in. Getting propagation modes wrong is a classic source of “why did my mount appear on the host” bugs, and it’s exactly the kind of nuance I double-check rather than trust to an AI suggestion verbatim.
Pro Tip: findmnt -o TARGET,PROPAGATION shows whether each mount is shared, private, or slave, which is the fastest way to debug surprising mount leakage.
Why Containers Are Just Namespaces Plus cgroups
Put the pieces together and the trick is exposed. A container is a process launched with a fresh set of namespaces (its own PID tree, network stack, mounts, hostname, and user mapping) whose resource usage is capped by cgroups (CPU shares, memory limits). The image is just a tarball unpacked and pivoted into via pivot_root inside the mount namespace. There is no hypervisor, no second kernel, no hardware virtualization. Docker, Podman, and containerd are orchestration and ergonomics around the same unshare/nsenter/clone primitives you just used by hand.
Understanding this changes how you debug production. When a container “can’t reach the database,” you now know to nsenter -n into it and check its routing table directly. When it “sees the wrong files,” you check its mount namespace and propagation.
If you want to go deeper, browse more guides in the Linux admins category, and grab reusable starting points from our prompt library or the curated prompt packs. For drafting these commands interactively I’ll often start a session in Claude AI or run a terminal-native assistant like Warp, and for quick lookups ChatGPT is fine too.
Keeping AI in the Loop, Not in Control
Tools like these are wonderful for generating iptables chains, decoding obscure unshare flag combinations, or explaining a propagation mode you’ve forgotten. But the same caution applies everywhere on this site: an AI assistant is a fast junior engineer. It accelerates you, it does not replace your judgment, and it must always have a human reviewing what it produces before anything touches a real system. Never hand it production credentials, never let it run privileged namespace surgery on a live host unsupervised. Run its suggestions in a throwaway VM first. If you want a more structured workflow, our incident response dashboard and code review dashboard are built around exactly that human-in-the-loop principle.
Conclusion
Namespaces are the quiet machinery under every container you run. With unshare you can build isolation from scratch, and with nsenter you can step into anything already running to debug it on its own terms. Spend an afternoon making veth pairs and remounting /proc by hand and containers stop being a black box forever. Lean on AI to move faster through the syntax, keep a human reviewing every privileged command, and keep your real credentials far away from the assistant. The kernel was doing the hard work all along.
Download the Free 500-Prompt DevOps AI Toolkit
500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.
- 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
- Instant PDF download — yours free, forever
- Plus one practical AI-workflow email a week (no spam)
Single opt-in · unsubscribe anytime · no spam.