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.

os functionPython 3.12+
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

NameTypeRequiredDescription
flagsintyesunshare: CLONE_* constants OR-ed together; 0 is a no-op.
fdint | object with fileno()yessetns: an open /proc/<pid>/ns/<type> file, or a pidfd from os.pidfd_open() (Linux 5.8+).
nstypeintno (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
True
2. 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
True
4. 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
True
5. 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.