Build a Small bwrap Sandbox Without Touching the Host
You will finish with a disposable bubblewrap sandbox that can see /usr read-only, has fresh /proc and /dev mounts, and cannot see an ordinary host file such as /etc/passwd. The examples use bubblewrap 0.9.0, from package version 0.9.0-1ubuntu0.3 on this machine.
The route
Jump straight to the step you need, or tick off Done means at the end.
Allow about fifteen minutes. You need a shell and the bubblewrap package. The commands are ordinary user commands. Do not add sudo as a first response to a failure: bwrap is designed to use user namespaces, and changing privilege changes the security model you are testing.
Safety boundary
This guide only starts a short-lived printf process. It does not mount anything in the host namespace, alter a service, create a persistent image, or change package or kernel settings.
1. Confirm the installed version
Check the executable before relying on examples from another release:
$ bwrap --version
bubblewrap 0.9.0
Option processing is positional for filesystem operations. In particular, the bind and mount actions are applied in the order given. Keep the source and destination paths explicit, and put -- before the command so its arguments cannot be mistaken for bwrap options.
Checkpoint
If bwrap --version is missing, stop here and install the distribution package through your normal change process. This guide does not cover package installation.
2. Assemble the smallest useful filesystem
Run this harmless probe. It creates a new empty root, makes /usr visible read-only, adds the kernel-provided mounts needed by many programs, and executes printf:
$ bwrap \
--ro-bind /usr /usr \
--symlink usr/lib64 /lib64 \
--proc /proc \
--dev /dev \
--tmpfs /tmp \
--clearenv \
--setenv PATH /usr/bin \
--chdir / \
-- /usr/bin/printf 'sandbox-ok\n'
sandbox-ok
The root filesystem starts as a private tmpfs, so a path is absent unless an option makes it visible. The --ro-bind action exposes the host directory without allowing writes through that mount. The new /tmp is in the sandbox and disappears when the last process exits.
The --symlink line is needed on systems where the dynamic linker expects /lib64 to point into /usr/lib64. If your distribution uses a different layout, inspect its existing symlinks and adapt the layout deliberately. Do not bind the whole host root merely to make a test pass.
3. Verify what the child can see
Replace the probe with a shell that checks both a visible path and an omitted one:
$ bwrap \
--ro-bind /usr /usr \
--symlink usr/lib64 /lib64 \
--proc /proc \
--dev /dev \
--tmpfs /tmp \
--clearenv \
--setenv PATH /usr/bin \
--chdir / \
-- /usr/bin/sh -c 'printf "cwd=%s\n" "$PWD"; test -x /usr/bin/sh; test ! -e /etc/passwd; printf "filesystem-check-ok\n"'
cwd=/
filesystem-check-ok
The exact PWD output can vary if the child shell does not preserve it, but the two tests should succeed. A missing /etc/passwd is expected in this deliberately small root. If you bind a host path later, re-run the check: every bind is a new trust decision.
4. Understand namespaces and network access
bwrap always creates a new mount namespace. Other namespaces are opt-in, except that an unprivileged installation may automatically require a user namespace. Add only the isolation you need. For example, this version also creates new PID and network namespaces:
$ bwrap \
--ro-bind /usr /usr \
--symlink usr/lib64 /lib64 \
--proc /proc \
--dev /dev \
--unshare-pid \
--unshare-net \
--new-session \
-- /usr/bin/sh -c 'printf "isolated-process\n"'
isolated-process
A new network namespace does not provide ordinary host networking. A new PID namespace changes which processes the child can see, and bwrap supplies a minimal reaper unless --as-pid-1 is requested. --new-session disconnects the child from the controlling terminal. It is a useful safety measure for interactive sandboxes; the manpage also recommends seccomp protection against terminal injection when it is not used.
5. Diagnose a user-namespace failure
On this machine the first sandbox probe fails before the child starts:
$ bwrap --ro-bind /usr /usr --dev /dev --proc /proc -- /usr/bin/printf 'sandbox-ok\n'
bwrap: setting up uid map: Permission denied
This means the current execution context did not permit the user-namespace setup. It is not evidence that the bind syntax is wrong. Check the status immediately:
$ bwrap --ro-bind /usr /usr --dev /dev --proc /proc -- /usr/bin/true
$ printf 'exit status: %s\n' "$?"
exit status: 1
Investigate the host or container policy that governs unprivileged user namespaces, and check whether you are already inside a restricted service or CI environment. Do not work around this by granting broad capabilities, enabling a setuid helper, or binding sensitive host sockets. If the policy cannot be changed, run the test in an approved environment or use the platform's supported sandbox wrapper.
6. Keep the boundary honest
bwrap constructs a sandbox; it does not choose a complete security policy for you. A writable bind exposes host state to the child, and a device bind grants device access. Binding a D-Bus socket can provide routes into host services. Prefer --ro-bind, use --bind-try only when an optional source is genuinely optional, and make network and IPC access explicit.
There is no persistent undo for the examples above. When the child exits, the private mounts are cleaned up. If you later add a bind to a long-running command, stop that child and remove the wrapper or service change that launched it. Review --seccomp separately: it loads a compiled filter through a file descriptor and is not a substitute for designing the filesystem and namespace layout.
Done means
- You confirmed the installed bubblewrap version and package.
- A harmless child printed
sandbox-okin an approved user-namespace environment. /usrwas exposed read-only and/tmpwas private.- You verified that an omitted host path was absent inside the sandbox.
- You chose PID, network and terminal isolation deliberately.
- You can distinguish a user-namespace policy failure from a bad child command.
- You have not granted extra privilege or exposed a host socket just to make the probe pass.