os.unshare
flags is a bit mask of CLONE_* constants combined with |; 0 changes nothing. Most namespaces need CAP_SYS_ADMIN, so an ordinary user gets PermissionError, except CLONE_NEWUSER where the kernel allows unprivileged user namespaces. Availability: unshare Linux 2.6.16+, setns Linux 3.0+ with glibc 2.14+; neither exists on macOS or Windows.
Common call
os.unshare(os.CLONE_NEWUSER | os.CLONE_NEWNS)
Returns
None
Replaces
the unshare and nsenter commands, or ctypes calls into libc
Watch out
It changes the calling process itself: do it in a child, not your main program
os.unshare(flags) / os.setns(fd, nstypenstype — setns: CLONE_NEW* constant(s) the fd must match; 0 means no check.type: int · default: 0=0)
→ None
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| flags | int | yes | unshare: CLONE_* constants OR-ed together; 0 is a no-op. |
| fd | int | object with fileno() | yes | setns: an open /proc/<pid>/ns/<type> file, or a pidfd from os.pidfd_open() (Linux 5.8+). |
| nstype | int | no (0) | setns: CLONE_NEW* constant(s) the fd must match; 0 means no check. |
Return value
None — Both change the namespaces of the calling process (setns: the calling thread).
Common patterns
Private hostname in a child (unprivileged)
A new user namespace first gives you the capabilities to create the others.
import os, socket if os.fork() == 0: os.unshare(os.CLONE_NEWUSER | os.CLONE_NEWUTS) socket.sethostname('sandbox') os._exit(0)
Join the network namespace of PID 1 (root)
The example from the docs.
import os fd = os.open('/proc/1/ns/net', os.O_RDONLY) os.setns(fd, os.CLONE_NEWNET) os.close(fd)
Join several namespaces of a process via pidfd
Linux 5.8+: one call for all the listed namespace types.
import os pidfd = os.pidfd_open(pid) os.setns(pidfd, os.CLONE_NEWUTS | os.CLONE_NEWPID) os.close(pidfd)
Examples
1. Linux only
import os, sys
hasattr(os, 'unshare') == hasattr(os, 'setns') == hasattr(os, 'CLONE_NEWNS') == (sys.platform == 'linux' and sys.version_info >= (3, 12))
Returns
True2. Combine flags with |
CLONE_NEWNS, CLONE_NEWUTS = 0x00020000, 0x04000000 # Linux values
hex(CLONE_NEWNS | CLONE_NEWUTS)
Returns
'0x4020000'3. Remove one flag from a mask
CLONE_NEWNS, CLONE_NEWUSER = 0x00020000, 0x10000000
flags = CLONE_NEWUSER | CLONE_NEWNS
flags & ~CLONE_NEWNS == CLONE_NEWUSER
Returns
True4. Every flag is a single bit
values = [0x400, 0x200, 0x2000000, 0x8000000, 0x40000000, 0x20000, 0x20000000, 0x80, 0x10000000, 0x4000000, 0x800, 0x40000, 0x10000, 0x100] # the 14 CLONE_* on Linux
all(v & (v - 1) == 0 for v in values) and len(set(values)) == 14
Returns
True5. EPERM: what an unprivileged unshare raises
import errno
(errno.EPERM, errno.errorcode[errno.EPERM])
Returns
(1, 'EPERM')Pitfalls
1. Adding flags with + instead of |
With | a repeated flag is harmless; with + it doubles into a different bit and asks for the wrong namespace.
+
CLONE_NEWNS = 0x00020000 hex(CLONE_NEWNS + CLONE_NEWNS)
'0x40000'
|
CLONE_NEWNS = 0x00020000 hex(CLONE_NEWNS | CLONE_NEWNS)
'0x20000'
2. Testing a flag with == instead of &
A mask usually holds several flags, so comparing the whole mask with one flag is False. Test the bit with &.
==
CLONE_NEWNS, CLONE_NEWUSER = 0x00020000, 0x10000000 flags = CLONE_NEWUSER | CLONE_NEWNS flags == CLONE_NEWUSER
False
&
CLONE_NEWNS, CLONE_NEWUSER = 0x00020000, 0x10000000 flags = CLONE_NEWUSER | CLONE_NEWNS bool(flags & CLONE_NEWUSER)
True
When to use
Use it
- Lightweight sandboxes and test isolation on Linux (private mounts, hostname, network)
- Entering the namespaces of a container with setns
Reach for something else
- Portable code → nothing comparable on macOS or Windows; use containers or subprocess sandboxes
- Full containers → podman / docker / bubblewrap do uid maps, mounts and cgroups for you
Notes
CPython impl
Direct wrappers of unshare(2) and setns(2); errors are raised as OSError subclasses (PermissionError for EPERM, OSError errno 22 for invalid flags)
Availability
unshare: Linux 2.6.16+; setns: Linux 3.0+ with glibc 2.14+; both added in Python 3.12. The CLONE_* constants exist only where the implementation supports them
Linux values
CLONE_NEWTIME 0x80, CLONE_VM 0x100, CLONE_FS 0x200, CLONE_FILES 0x400, CLONE_SIGHAND 0x800, CLONE_THREAD 0x10000, CLONE_NEWNS 0x20000, CLONE_SYSVSEM 0x40000, CLONE_NEWCGROUP 0x2000000, CLONE_NEWUTS 0x4000000, CLONE_NEWIPC 0x8000000, CLONE_NEWUSER 0x10000000, CLONE_NEWPID 0x20000000, CLONE_NEWNET 0x40000000
Process-wide
unshare changes the caller for the rest of its life, and a new PID namespace applies only to children created afterwards (the caller keeps its pid). Do it in a child process so the parent keeps its normal view
Privileges
On our Linux check, unshare(CLONE_NEWNS) as a normal user raised PermissionError, while unshare(CLONE_NEWUSER) succeeded (uid became 65534) and a following unshare(CLONE_NEWUTS) then worked. Distributions can disable unprivileged user namespaces
FAQ
unshare, setns and the CLONE_* constants exist only on Linux (Python 3.12+), and they change the namespaces of the running process. The examples show the flag arithmetic with the Linux values, and run anything real in a child process.