Brutalist build environments and sandboxing

August 6, 2025

broot is a small utility I wrote specialized to build sandboxing on linux. A certain amount can be done with containers already, but I wanted something simpler and more specialized, with the aim of being able to check out a codebase, drop network access (because blindly pulling dependencies from the internet is dumb), and then build anything within that codebase (most of my personal code is in a monorepo).

Lets run it to see what options it presents.

$ broot
usage: broot: brutalist build sandbox

Required flags
    --buildroot      - path to folder containing the root filesystem
    --src            - path to folder containing source code to mount
    --out            - path to folder for writing output
    --src-bind       - path inside the build environment to bind source directory
    --out-bind       - path inside the build environment to bind output directory
    -- COMMAND       - the command to run inside the sandbox

Other flags
    --tmpfs-bind     - path inside the build environment to bind a tmpfs
    --env            - specify an environment variable with form VAR=VALUE
    --help           - display this message

Subcommands
    check            - check the binary has the needed capabilities to execute
    help             - display this message

The intention is that this be used inside scripts / Makefiles, and not necessarily be driven by a human. The options are minimal and specialized specifically to build sandboxing.

If binfmt support with the appropriate architecture emulation is installed, the root filesystem can be for a different architecture, and everything still works (I’m hoping to use this to help test my compiler).

The intention here is that this be driven by a higher level script/tool. broot itself needs CAP_SYS_ADMIN to do the syscalls (which it drops before running the internal command), so keeping its surface minimal is generally a good idea. It’s around 600 lines of code, the bulk of which is command line parsing and alternatives for libc procedures, so can be easily audited, and linked statically to make it easy to distribute.

An example workflow

My first test case has been compiling a small game project. The root filesystem was constructed using debootstrap, and chroot-ing in to install dependencies, much like here. This filesystem was then tarballed and gzipped, and put in the games data folder - with binary blob support in your version control system (e.g. git-lfs, which I’m no lover of), this can be checked in.

The build script checks for an extracted filesystem, and if it doesn’t exist, extracts the tarball, before calling itself (with slightly different parameters) via broot. Subsequent builds only incur a small overhead (order of milliseconds) from running via broot.

This means a build workflow of a new project can just be git clone followed by running the build script - no other access required!

This isn’t entirely robust, since changes to the buildroot tarball won’t be detected and update the unpacked filesystem, but it’s not difficult to imagine how this can be fixed, and the overall process improved.

Closing thoughts

This approach is very minimal, but rather than requiring specialized image formats, can just make use of a directory on the filesystem. This makes iteration on the filesystem easy. It also lets you manage these filesystems in any way you choose. Archiving tarballs either within the version control system, or on a simple NFS drive / server somewhere is a tried-and-true way of managing things, and likely to survive for some years. One can avoid the largely unnecesary churn of container image formats, needing to run container registries, or depending on external images of unknown life expectancy.

Ultimately, it’s still convenient to have all the dependencies you need installed on your system for day-to-day work, even though you can perfectly well launch an iteractive shell using broot. Still, having these build environments makes it easier to get up and running quickly, work cross architecure, and of course run builds and tests and be certain all dependencies are captured.

Source code

An initial snapshot of the sourcecode can be found here.