Latest / Linux Server Admin with Fexingo: Sysadmin, Bash, and Server Engineering / How to Use Linux Namespaces for Process Isolation
Transcript
- Lucas: So you want process isolation without pulling in Docker or Podman. Maybe you need to run a legacy app that only works on an old library stack, or you want to sandbox a risky test script without spinning up a whole VM. Linux namespaces can do that. They are the kernel primitive that containers are built on, but you can use them directly — and it's surprisingly simple. Luna: I think a lot of people hear 'namespace' and immediately jump to Docker. But you're saying we can create isolated environments with just a couple of commands? Lucas: Exactly. Namespaces are what give containers their illusion of isolation — separate process trees, separate network stacks, separate mount points. You can create them with the 'unshare' command, and you can join existing namespaces with 'nsenter'. No daemon required, no image layers, no overhead beyond the kernel. Luna: Let's start with the most common one — PID namespaces. How do I create a new process namespace? Lucas: The simplest way is: 'unshare --fork --pid --mount-proc /bin/bash'. That tells the kernel to create a new PID namespace and a new mount namespace, then mount a fresh /proc inside it so 'ps' shows only the processes in that namespace. When you run that, you get a shell where PID 1 is bash. If you run 'ps aux', you'll see just bash and ps. The host's processes are invisible. Luna: Wait — does that mean if I kill that bash shell, all processes in the namespace die? Since bash is PID 1? Lucas: That's exactly the gotcha. In a PID namespace, the process with PID 1 has special semantics — if it exits, the kernel sends SIGKILL to all other processes in that namespace. So if your init process is a shell, and you type 'exit', everything you spawned inside gets killed. That's actually useful for cleanup, but it means you need to be careful if you have long-running processes. You might want a proper init like 'tini' or just use '--kill-child' which unshare offers. Luna: Right, so if I want to run a background job, I should use '--kill-child' or start a simple init. What about network namespaces? Lucas: Network namespaces are almost more useful day-to-day. You can create one with 'ip netns add mynetns', then run a process inside it with 'ip netns exec mynetns command'. By default, the new namespace has no network interfaces except loopback — which is down. So you have complete isolation. You can then create virtual Ethernet pairs — veth pairs — to connect the namespace to the host or to a bridge. Luna: So I could run a web server in a network namespace that only has loopback, and then use iptables or a veth pair to expose only port 8080 to the host? Lucas: Precisely. I did exactly that a few months ago for an old internal tool that needed to listen on port 80 but also had a hardcoded dependency on an ancient libssl. I created a network namespace, moved a physical NIC into it — actually I used a macvlan — and the tool thought it had the whole machine. Meanwhile the host's network stack was untouched. No Dockerfile, no container runtime, just two commands. Luna: That's elegant. But how do you inspect which namespaces exist on a system? If you create one with 'unshare' and detach, can you find it later? Lucas: Every process belongs to a set of namespaces. You can see them in /proc/PID/ns/. For example, 'ls -l /proc/self/ns/' shows symlinks like 'pid:' and 'net:'. The numbers in brackets are the namespace identifiers. If two processes share the same number, they are in the same namespace. You can also use 'nsenter' to join a namespace by PID: 'nsenter --target PID --mount --uts --ipc --pid /bin/bash'. Luna: So if I have a long-running process that I started inside a namespace, I can later 'nsenter' its PID and get a shell inside the same namespace. That's good for debugging. Lucas: Exactly. One thing to watch out for: if you create a PID namespace with 'unshare' but don't mount a new /proc, the 'ps' command inside will still show the host's processes because it reads /proc from the host mount namespace. That's why the '--mount-proc' flag is standard practice. Otherwise you get a false sense of isolation — you think you're alone but the kernel still sees everything. Luna: Are there other namespace types beyond PID and net? I know Docker uses several. Lucas: Linux currently has eight — PID, net, mount, UTS, IPC, user, cgroup, and time. The user namespace is especially powerful: it lets an unprivileged user map their UID 0 inside the namespace to a non-root UID outside, effectively granting root inside without actual root on the host. That's how rootless containers work. But for most sysadmin tasks, PID and net are the workhorses. Luna: Let's talk about a practical scenario. Say I have a script that might eat all memory or write junk files. How would I isolate it with namespaces? Lucas: I'd combine PID, mount, and network namespaces. Start with 'unshare --fork --pid --mount --net /bin/bash'. Inside, mount a tmpfs on /tmp, maybe remount / as read-only if you can. The script can't see other processes, can't touch the host network, and if it fills up /tmp, it only fills the tmpfs. When you exit the shell, everything goes away. No cleanup needed. Luna: What about file system isolation? You mentioned mount namespaces, but can I avoid mounting the whole host root? I want a minimal filesystem. Lucas: You can use 'pivot_root' or 'chroot' inside the mount namespace to switch to a different root directory. For example, you could have a minimal Debian chroot at /var/chroot/jail, then inside the mount namespace call 'chroot /var/chroot/jail /bin/bash'. Combine that with a PID namespace and the process thinks it's in a complete, separate system. That's essentially how a container runtime sets up the rootfs. Luna: So namespaces plus chroot is almost a container. But without the image management, without the registry, without the overlay filesystem. Lucas: Right. It's more work, but it's also more transparent. You control every layer. And for short-lived tasks, it's often faster than pulling a container image. I've seen teams use this for running untrusted user code in a sandbox — no Docker socket exposed, no daemon to harden. Luna: I want to test this. Can I create a network namespace, give it an IP, and ping the host? Lucas: Sure. On the host, run 'ip netns add testns'. Then 'ip netns exec testns ip link set lo up' to bring up loopback. To connect to the host, create a veth pair: 'ip link add veth0 type veth peer name veth1'. Move one side into the namespace: 'ip link set veth1 netns testns'. Assign IPs on both sides: 'ip addr add 10.0.0.1/24 dev veth0' on host, and 'ip netns exec testns ip addr add 10.0.0.2/24 dev veth1'. Then bring both up. You should be able to ping 10.0.0.2 from host and 10.0.0.1 from inside the namespace. Luna: That's straightforward. And if I want the namespace to have internet access, I'd set up NAT on the host, right? Lucas: Exactly. You'd add a default route inside the namespace pointing to the host's veth IP, and on the host enable IP forwarding and a masquerade rule in iptables. It's the same pattern as Docker bridge networking, just manual. That's actually a great learning exercise — once you've done it manually, the Docker abstraction makes much more sense. Luna: One thing that worries me: if I create a namespace and the process inside crashes, does the namespace persist? Could I leak namespaces? Lucas: Namespaces are reference-counted: they exist as long as at least one process is attached. If all processes in a PID namespace exit, the namespace is destroyed. But if you create a network namespace with 'ip netns add', it persists as a bind mount at /var/run/netns/ — that keeps a reference alive even if no process is in it. So you can have persistent network namespaces that survive reboots if you configure them that way. To clean them up, 'ip netns delete testns' removes the bind mount. Luna: Good. So 'ip netns' namespaces are durable, but PID namespaces from 'unshare' are ephemeral. That distinction is important. Lucas: Yes. And if you use 'unshare' without '--fork', the current process joins a new namespace but the original shell may still have references. I always use '--fork' to spawn a child in the new namespace. Luna: One more thing — can I nest namespaces? Like a PID namespace inside another PID namespace? Lucas: Yes, with a caveat. PID namespaces are hierarchical. A process inside a child namespace can see processes in that child namespace, but not in the parent. The parent can see all children, though. However, if you nest PID namespaces, you cannot use 'nsenter' from the host to enter a deeply nested namespace — you have to go through the intermediate namespace. And each level of nesting adds some overhead because of the PID mapping. But it's possible, and container runtimes sometimes use nested PID namespaces for extra isolation. Luna: Alright, I'm convinced. Let's recap the key commands: 'unshare' to create, 'nsenter' to join, 'ip netns' for network namespaces, and 'ls /proc/PID/ns/' to inspect. Lucas: That's the core toolkit. One last tip: if you want to run a command as a daemon in a new namespace, you can combine 'unshare' with 'setsid' to detach from the terminal. Something like 'unshare --fork --pid --mount-proc setsid command &'. That way the command runs in the background with its own init process. Luna: I'm going to try isolating my next risky experiment with namespaces instead of Docker. It feels more surgical. Lucas: And you'll understand exactly what's happening under the hood. That's the beauty of namespaces — they demystify containerization. Once you've used them directly, Docker is just a convenience layer. Luna: Quick honest thing — we keep this show ad-free because a handful of listeners chip in monthly through buy me a coffee dot com slash fexingo. That's literally what funds making this many episodes. If today's tech conversation gave you something usable, that's where you can keep it going. Lucas: Yeah, it's a small group, but it makes a real difference. We're grateful for every contribution. Luna: Alright, back to namespaces. One more edge case — what about UTS namespaces for hostname isolation? When would you use that? Lucas: UTS namespaces let each namespace have its own hostname and domainname. That's useful if you're running multiple services that need to see different hostnames — for example, testing email delivery or SSL certificates. You can create a UTS namespace with 'unshare --uts /bin/bash', then 'hostname newname' inside. The host's hostname is unaffected. Luna: So you could simulate a different server identity without changing the actual host. Lucas: Exactly. And combined with network namespaces, you can create a fully isolated testing environment on a single machine. No VMs, no container orchestration. Just the kernel doing what it does best.