A lighter-weight isolation layer

Containers with seccomp can enforce far lighter isolation than the hardware virtualization of virtual machines, but the trade-off is that they demand much more deliberate configuration. Both primitives operate at the operating system level: containers rely on kernel features such as namespaces, cgroups, and privilege dropping, while seccomp restricts which system calls a process can issue. To evaluate whether either one is appropriate as a sandbox, two questions matter: Can a malicious workload inside the sandbox escape to the host? And if it cannot escape, can it still reach other systems or otherwise misuse the host’s permissions?

The container-to-host attack surface

Three components—the runtime implementation, the kernel primitives and interfaces available to it, and the runtime configuration—define a container's breakout surface. Containers are not inherently secure sandboxes: a kernel vulnerability, a bug in the runtime, or a misconfiguration can let an untrusted workload modify files and execute code on the host. Different runtimes make use of different primitives; Docker on Linux defaults to the runC runtime but can employ namespaces, cgroups, privilege dropping, SELinux or AppArmor, and seccomp to establish isolation properties.

Unlike commodity VM setups, container hardening is more your responsibility than the vendor's. Many tools have improved their defaults, which may work for some use cases, but it is on you to verify configuration and tighten it for your security needs.

Dealing with a compromised container

Putting a potentially compromised workload inside a container is not enough for security. Configuration must actively prevent host takeover, and your infrastructure should minimize the blast radius of any one container. One recommended pattern: place containers on an isolated network without mounted credentials, network devices, extra filesystem access, and so on; then pass input and collect output through a controlled orchestration channel.

Dangerous syscalls and what seccomp disallows

Seccomp can restrict only which syscalls a process uses—so it makes sense primarily for workloads that are pure compute. The idea is that, in many jobs, the program needs no dynamic filesystem access and no network calls, so you can rely on blocking these entirely while allowing only memory allocation, output on an already-open file descriptor, clock reads, and exit. Seccomp often sits on top of containers as a second defense layer, notably in tools like nsjail and firejail, and appears in widely deployed applications such as Android, Chrome, and Firefox.

There are real constraints, however, and identifying which system calls are safe is non-trivial. The default Linux seccomp mode permits only exit, sigreturn, read and write—barely enough to run any realistic program. The problem is that what engineers tend to think of as pure compute requires many more calls, such as allocating heap memory or knowing the current time. Every allowed syscall widens kernel attack surface, so teams must rewrite programs to avoid dangerous ones or stack additional sandboxing. Figma, for instance, allows programs to write to already-open file descriptors, allocate memory, fetch the current time, and exit—while deliberately evading the more complex kernel areas such as the filesystem, network, sockets, and keychain.

Containers and Seccomp: Deliberate Design, Not Drop-In Security

Applying the evaluation framework from our introduction, containers and seccomp reveal themselves as tools that demand significantly more engineering attention than VMs. Both provide powerful isolation primitives, but their security, performance, and operational profiles are defined by configuration details rather than default behavior.

Environment Constraints

Containers present a more restricted runtime environment out of the box than VMs. Programs with specialized hardware, operating system, or rendering requirements may fail outright or need modification to execute correctly. Seccomp-based systems impose a different constraint: the workload must run on a Linux kernel that supports seccomp, which is a baseline requirement rather than a configurable option.

Security and Performance Complexity

Directly comparing containers versus seccomp on security and performance is nuanced. For containers, the conversation often centers on kernel attack surface. A hypervisor’s attack surface is typically smaller than an OS kernel’s, and incidents like Dirty COW and Dirty Pipe demonstrate real container escape vectors. However, hypervisor bugs themselves can enable breakout, and technologies like gVisor reduce container attack surface by inserting a hardened kernel between the OS kernel and the container process. VMs provide strong isolation with relatively few configuration options but carry higher performance overhead and require bring-your-own orchestration. Containerization can achieve comparable isolation with fine-grained controls, provided every detail is configured correctly.

Performance for containers depends heavily on the granularity of isolation required. Setup and teardown can be quick: nsjail startup typically takes tens to low hundreds of milliseconds, though a long tail exists and language runtime initialization can take substantially longer.

Seccomp-only sandboxes are similarly hard to benchmark in the abstract. Isolation strength depends entirely on the kernel's syscall interface and the configured allowlist. Allowing ptrace on older Linux kernels, for instance, can permit sandbox escape. With a minimal syscall allowlist, strong isolation is achievable. Since syscall filtering is inexpensive, seccomp-only sandboxes generally incur significantly less performance overhead than hypervisor- or container-based solutions.

Development Costs and Friction

Adopting container-based sandboxes presents two principal challenges: configuration and orchestration. Correctly securing a container boundary requires technical expertise and experimentation across a checklist of practices—dropping privileges, creating new Linux namespaces, restricting mount points, and more. Understanding what each defense guarantees—and what it does not—is essential. Safely sandboxing a complex program demands deep knowledge of its filesystem access patterns, required capabilities, and system resource interactions. Iterative trial-and-error to reach a functional and secure configuration is common and can represent substantial work. Beyond security configuration, you may need to build systems for worker pool management, safe workload data input, and secure output extraction for both VMs and containers.

Seccomp-only sandboxes require even more customization than VMs or containers, which can often serve as drop-in solutions. The engineering cost scales with how suitable seccomp is for the target program. Constructing the syscall allowlist requires knowing all possible syscalls the program can make, typically by empirically running the program with a tool like strace across a representative input corpus to exercise all code paths.

A notable seccomp constraint is that it only filters syscall arguments at the top level and cannot dereference pointer arguments. This prevents selective filtering of syscalls like openat based on the resource being accessed. In response, you may need to rewrite the program so it performs risky syscalls before processing untrusted user input. Such rewriting is viable only when you control the source code, which may not be practical for commodity programs.

One advantage of seccomp-only sandboxes is direct invocation of the sandboxed program without additional orchestration or plumbing.

Maintenance and Operational Overhead

Both containers and seccomp consume operational resources, and updates to the program or new feature development often involve engineers outside the security team. Seccomp allowlists are notably brittle. New program behavior may require adding syscalls that do not materially increase attack surface, or significant rewriting if those syscalls do present unacceptable risk.

Monitoring and debugging seccomp-only sandboxes is particularly difficult. Kernel logs indicate when a process is killed by seccomp and which syscall caused the failure, but they offer little context. Root causes can be diverse: an overlooked syscall in a rarely used code path, newly introduced behavior requiring an additional syscall, an upgraded library or kernel, architectural differences between test and production environments, or even malicious exploitation in rare cases. Robust continuous integration testing can catch some of these issues early, but not all.

Container sandboxing in production at Figma

Figma's container-level isolation work is built around nsjail, which the company adopted after weighing it against a Docker-based approach. The primary target is RenderServer, a C++ server-side version of the Figma editor used for tasks such as thumbnailing. RenderServer depends on GPU acceleration and is invoked by multiple backend services.

Docker was considered but rejected early on. Implementing it would have meant building a new sandboxing service around a secure Docker configuration, an orchestration layer to manage that service, and re-architecting existing services to call it over the network. nsjail, by contrast, worked as a drop-in wrapper and let the team focus on hardening the configuration rather than on service plumbing.

How the nsjail sandbox is configured

Each user request triggers a fresh RenderServer process inside new user, PID, mount, and network namespaces, with networking disabled entirely. Filesystem access is limited to specific mount points: the input file, the required libraries, and the output directory. A seccomp-bpf policy restricts the process to a strict allowlist of syscalls.

Production rollout surfaced two configuration issues. First, job errors correlated strongly with input files containing large images; the output files were coming out at exactly 1 MB. The cause was nsjail's default rlimit_fsize of 1 MB, which the team had overlooked in the documentation. A quick fix resolved it. Second, the seccomp allowlist needed several updates as rare code paths in RenderServer — paths that never executed during testing — were hit at production scale.

Dropping containers for seccomp only

For RenderServer workloads that don't require GPU acceleration, Figma eventually moved to a seccomp-only sandbox to cut both performance overhead and the operational complexity of nsjail. The main obstacle was filesystem access: seccomp alone cannot distinguish between a legitimate open of an output file and a malicious open of a sensitive file elsewhere on the system, and RenderServer performs file I/O across many feature code paths.

Consider an SVG export flow. The risky phase is processing the Figma file itself, which may contain attacker-controlled input that is handled by RenderServer and third-party C++ libraries. Ideally, a seccomp filter would be applied before that processing begins, blocking syscalls like openat so a compromised process can't read arbitrary files. But applying such a filter early would also kill the process when it tries to open its own output file.

The solution was a significant refactor: file opens were reordered so that all of them happen before any processing of potentially dangerous user input. With that done, a restrictive libseccomp filter could be applied, confining RenderServer to reading from and writing to only its permitted resources. The resulting seccomp-only version is easier to test and debug and performs considerably better than the nsjail version. The trade-off is that RenderServer is now locked into a single-threaded model and cannot dynamically load fonts or images later in its runtime, a constraint engineers must respect when adding features.

Both container-based and seccomp-only sandboxes offer lighter-weight alternatives to full virtual machine isolation, but each carries distinct development and maintenance costs. Figma's security team has also published an introduction to server-side sandboxingA millipede gets into a castle by going up through the drain into a pit with spikes. The pit stops it from getting out. and a deep dive on virtual machinesA castle has two large towers on either side of one small tower in the middle. The middle tower projects an image of another castle with a millipede in it. for those comparing the options.