bubblewrap (bwrap): The Unprivileged Sandbox That Powers Flatpak, In One Command

2026-09-05

You want to run something suspicious. Or a build script from a fresh clone. Or just a shell that can't read ~/.ssh. The mainstream answers all suck: Docker requires a daemon and root at install time, chroot needs root and can't hide the network, unshare(1) gives you namespaces but no bind mounts or seccomp. Firejail wants a profile file per program.

bubblewrap (bwrap) is the tool Alexander Larsson wrote for Flatpak. It's a setuid-safe launcher that assembles a fresh mount namespace, user namespace, PID namespace, seccomp filter, and process tree — in a single argv. No daemon. No config files. No root required (on any modern kernel with unprivileged user namespaces).

The simplest useful invocation — a shell that sees the host read-only but has a private /tmp and no network:

bwrap \
  --ro-bind / / \
  --dev /dev --proc /proc --tmpfs /tmp \
  --unshare-all --share-net=false \
  --die-with-parent \
  bash

That's it. It starts in ~5 ms. rm -rf / inside it does nothing to the host because / is a read-only bind.

Where it earns its keep is composing narrower jails. Run an npm install that can see the project but nothing else in your home:

bwrap \
  --ro-bind /usr /usr --ro-bind /etc /etc \
  --symlink usr/bin /bin --symlink usr/lib /lib --symlink usr/lib64 /lib64 \
  --proc /proc --dev /dev --tmpfs /tmp \
  --bind "$PWD" /work --chdir /work \
  --setenv HOME /work --setenv PATH /usr/bin \
  --unshare-all --share-net \
  --die-with-parent \
  npm install

The install script cannot read ~/.ssh/id_ed25519, cannot see your gpg-agent socket, cannot write to ~/.bashrc. It can hit the network (because --share-net), and can only write to /work and /tmp.

Some flags that are hard to appreciate until you need them:

Compare with unshare: unshare gives you namespaces, but the moment you want a read-only /usr and a fresh /tmp you're writing a mount script that runs inside unshare --mount. Bubblewrap collapses that into declarative flags and — critically — does the pivot_root dance correctly, dropping capabilities in the right order so the child never inherits CAP_SYS_ADMIN in the initial namespace.

Compare with Docker: no image, no layer cache to prune, no daemon socket that's effectively root-equivalent, no iptables chains being rewritten behind your back. It's just a process.

Ships in Debian, Fedora, Arch, Alpine, Nix. Zero runtime dependencies beyond libc.

Key Takeaway: When you need a real sandbox but not a container runtime, bwrap gives you namespaces, bind mounts, and seccomp declaratively in one argv — no daemon, no root, no config file.

All newsletters