setns(2)
Move the calling thread into an existing namespace, given an fd for it (or a pidfd).
- Since
- Linux 3.0check your kernel →
- glibc
- glibc 2.14
- System
- Linux
- Man page
- setns(2)
Synopsis
#define _GNU_SOURCE /* See feature_test_macros(7) */
#include <sched.h>
int setns(int fd, int nstype);Rod's take
Containers aren't magic, they're namespace flags, and setns(2) is how you walk into one from the outside. nsenter opens the files under /proc/PID/ns and calls setns, so you can run the host's tcpdump and ss inside a distroless container's network namespace without installing a damn thing in the image. Join only the namespace you need; skip the mount namespace and you keep your own tools while /proc/PID/root shows you the container's files. Hold an fd on the ns file and the namespace sticks around for forensics after its processes are gone. Root required for most of it, as it should be.
Flags and arguments that matter
| Name | What it does |
|---|---|
| 0 | Join whatever namespace type fd refers to; fine when you don't need the type checked. |
| CLONE_NEWNETLinux 3.0 | fd must refer to a network namespace. |
| CLONE_NEWIPCLinux 3.0 | fd must refer to an IPC namespace. |
| CLONE_NEWUTSLinux 3.0 | fd must refer to a UTS namespace. |
| CLONE_NEWNSLinux 3.8 | Mount namespace. Needs CAP_SYS_CHROOT and CAP_SYS_ADMIN; caller must not share CLONE_FS state. |
| CLONE_NEWPIDLinux 3.8 | Descendant PID namespace. Only affects children created afterwards, not the caller. |
| CLONE_NEWUSERLinux 3.8 | User namespace. Caller must be single-threaded; needs CAP_SYS_ADMIN in the target namespace. |
| CLONE_NEWCGROUPLinux 4.6 | fd must refer to a cgroup namespace. |
| CLONE_NEWTIMELinux 5.8 | fd must refer to a time namespace. |
Errors you will actually hit
| errno | Meaning |
|---|---|
| EPERM | The calling thread lacks the capability this namespace type requires. |
| EINVAL | Namespace type doesn't match nstype, ancestor PID ns, or multithreaded caller joining a user ns. |
| EBADF | fd is not a valid file descriptor. |
| ESRCH | fd is a pidfd but that process has terminated and been waited on. |
| ENOMEM | Cannot allocate memory to change the namespace. |
Example
pid=$(docker inspect --format '{{.State.Pid}}' mycontainer)
# container's network stack, host's binaries
sudo nsenter -t "$pid" --net -- ss -tulpn
sudo nsenter -t "$pid" --net -- tcpdump -i eth0 -n
# distroless image: its pid/net/ipc/uts namespaces, host's /bin/sh
sudo nsenter -t "$pid" --pid --net --ipc --uts -- /bin/sh
# what nsenter calls setns(2) on
ls -la /proc/"$pid"/ns/Trimmed from the full walkthrough in Your container is six clone(2) flags. nsenter gets you back in..
BSD equivalent
FreeBSD: no namespaces; jail_attach(2) (FreeBSD 5.1) is the closest thing to joining an existing isolated environment. OpenBSD: none.
The long version, from the console

Your container is six clone(2) flags. nsenter gets you back in.
Containers are six kernel namespaces and nothing else. nsenter gets you inside from the host without docker exec, without touching the image, and without whatever tooling the vendor decided to ship.

setns and unshare: moving between Linux namespaces
Every container runtime in existence calls setns(2) and unshare(2). These two syscalls are how Linux carves off namespaced views of mounts, PIDs, networking, and hostnames. Here is what they actually do and where they will surprise you.
Sources
Version numbers are read off the cited man pages, not memory. Spot one that's wrong? Tell Rod.