Overview
Faber is an HTTP service that runs commands in Linux sandboxes. You send it a list of steps as JSON; it creates a fresh, empty workspace, runs the steps in order, and answers with each task’s stdout, stderr, exit code and resource usage. Nothing survives the request.
It is built for workloads where the code is not yours: grading programming submissions, online judges, running snippets from users or language models, compiling and testing code in a throwaway environment.
How a request runs
Section titled “How a request runs”client ──HTTP──▶ faber (API process) │ admission: API key, size and fan-out limits, task slots ▼ jailer (the faber binary re-executed, one per request) │ new mount, network, UTS and IPC namespaces │ workspace /faber and /tmp as small tmpfs mounts │ read-only toolchain paths from the image ▼ task supervisor ──▶ task process one per task: own PID and user namespace, own cgroup (memory, pids, cpu), rlimits, seccomp filter, no capabilities- The API process checks the API key and the request shape, and reserves one
task slot per task of the widest step. If no slots are free it answers
503withRetry-Afterinstead of queueing. - A jailer process is started for the request. It holds none of the service’s memory, environment or file descriptors, runs under a host UID leased to this request, and builds the sandbox root.
- Each task runs in its own PID and user namespace and cgroup, with resource limits, a seccomp policy and no capabilities. The steps share the workspace, so a later step sees the files an earlier step wrote.
- The results are collected, the sandbox and its cgroups are removed, and the response is sent.
The execution model covers steps, parallel tasks and failure handling. Sandbox and isolation describes the boundary in detail, and how Faber works walks through all of it with diagrams.
What you get back
Section titled “What you get back”For each task: stdout, stderr, exit_code, whether it was replayed from the
cache, and stats: peak memory, CPU time, peak process count, wall time, an
outcome (exited, timed_out, out_of_memory, policy_violation, …) and
whether output was truncated. A task that misbehaves never turns into an HTTP
error; the request still succeeds and the task’s outcome tells you what
happened.
Pieces
Section titled “Pieces”| Piece | What it is |
|---|---|
| Faber service | One Rust binary (/opt/faber) in the vgfractal/faber image. Listens on port 3000, API under /api/v1 |
| Toolchains | Whatever compilers and interpreters you install in the image. Tasks see them read-only. The Dockerfile builder writes the image for you |
| Step cache | Optional. Replays deterministic leading steps (typically compilation) instead of running them again; can be shared between instances through Redis |
| File store | Content-addressed uploads that tasks can reference by ID instead of inlining large inputs |
| SDKs | Clients for TypeScript, Rust, Go, Python and PHP |
Requirements
Section titled “Requirements”- Linux 5.8 or newer with cgroup v2 (the unified hierarchy).
- Rootful Docker (or Kubernetes) able to run a privileged container with
the host cgroup namespace and
/sys/fs/cgroupmounted read-write. Faber creates namespaces and cgroups itself and refuses to start without them. - x86_64 or ARM64. Both are tested on every change.
What Faber does not protect against
Section titled “What Faber does not protect against”There is one API key scope: every key holder can run code, read every stored file and clear the cache. Faber does not separate tenants.
- How Faber works: a request, the sandbox, the limits and the cache, in plain terms and with diagrams.
- Quick start: run Faber and send a request.
- Your first task: compile and test a program, field by field.