Skip to content

Isolation Levels

tenement provides multiple isolation levels for different security needs.

IsolationToolOverheadStartupUse Case
processbare~0<10msSame trust boundary, debugging
namespaceunshare~0<10msDefault - trusted code, /proc isolated
sandboxgVisor~20MB<100msUntrusted/multi-tenant code
firecrackermicroVM~128MB~125msCompliance, custom kernel
[service.debug]
command = "./app"
isolation = "process"

Runs as a bare process with no isolation. All processes see the same /proc, environment, etc.

When to use:

  • Trusted code only
  • Debugging
  • Same security boundary as the host

Overhead: None (bare metal speed)

[service.api]
command = "./app"
isolation = "namespace"

Uses Linux namespaces (PID + Mount) to give each process its own /proc and isolated mount namespace. Environment variables are hidden between services.

What’s isolated:

  • /proc - Process tree hidden
  • /sys - System interface hidden
  • Mount namespace - Filesystem views separated

What’s shared:

  • Network (unless configured otherwise)
  • System calls directly to kernel

Overhead: ~0 (kernel built-in since 2008)

Startup: <10ms

Requirements: Linux only

When to use:

  • Multi-tenant deployments (trusted code)
  • Microservices on one host
  • You want isolation without performance cost
  • Default recommendation for most users

Example: Multi-tenant with Namespace Isolation

Section titled “Example: Multi-tenant with Namespace Isolation”
[service.api]
command = "uv run python app.py"
health = "/health"
isolation = "namespace"
[service.api.env]
DATABASE_PATH = "{data_dir}/{id}/app.db"

Each tenant process:

  • Sees only its own /proc
  • Can’t spy on sibling processes
  • Can’t access sibling environment variables
  • Runs at native speed
[service.untrusted]
command = "./user-plugin"
isolation = "sandbox"

Uses gVisor (runsc) to filter system calls. Untrusted code runs in a syscall sandbox.

What’s blocked:

  • Kernel module loading
  • Raw socket access
  • Dangerous syscalls (ptrace, etc.)
  • Direct hardware access

Overhead: ~20MB memory per instance

Startup: <100ms (slightly slower, but cold-start)

Requirements:

  • Linux
  • gVisor installed (apt install runsc or similar)
  • Compile with --features sandbox

When to use:

  • User-supplied plugins/code
  • Third-party integrations you don’t trust
  • Multi-tenant + untrusted code
  • Compliance requirements
[service.api]
command = "./api"
isolation = "namespace"
[service.plugin]
command = "./user-plugin"
isolation = "sandbox" # Untrusted
memory_limit_mb = 128 # Extra constrained
cpu_shares = 50 # Limited CPU

API runs in namespace isolation (trusted, fast). User plugins run in gVisor sandbox (untrusted, safe).

MicroVM isolation with Firecracker. ~128MB overhead, compliance-grade isolation.

Planned for future releases.

Start
┌───────────────────────┐
│ Is the code trusted? │
│ (your own code, not │
│ user-uploaded) │
└──────────┬────────────┘
┌────────┴────────┐
│ │
Yes No
│ │
▼ ▼
┌───────────┐ ┌───────────────┐
│ Need /proc│ │ Use SANDBOX │
│ isolation?│ │ (gVisor) │
└─────┬─────┘ └───────────────┘
┌─────┴─────┐
│ │
Yes No
│ │
▼ ▼
┌──────────┐ ┌──────────┐
│NAMESPACE │ │ PROCESS │
│ (default)│ │(no isol.)│
└──────────┘ └──────────┘

Quick decision:

  • Trusted code + multi-tenantnamespace (default)
  • Trusted code + debuggingprocess
  • Untrusted codesandbox
  • Compliance/custom kernelfirecracker or qemu

Use namespace isolation

  • Cheap, fast, good isolation
  • Each tenant can’t see others
[service.api]
isolation = "namespace"

Use sandbox isolation

  • Extra security for untrusted code
  • 20MB overhead is worth it
[service.user_code]
isolation = "sandbox"
memory_limit_mb = 256
cpu_shares = 100

Use both

  • Trusted services: namespace
  • Untrusted services: sandbox
[service.api]
isolation = "namespace" # Your code
[service.user_plugins]
isolation = "sandbox" # Their code

Use bare process

  • Easiest to debug
  • Don’t need isolation locally
[service.debug]
isolation = "process"
  • What it protects against: Process inspection, environment snooping
  • What it doesn’t protect: OS-level exploits, kernel bugs
  • Best for: Trusted code separation (multi-tenant with your own apps)
  • What it protects against: Most user-space exploits, kernel-facing attacks
  • What it doesn’t protect: Bugs in gVisor itself, hardware exploits
  • Best for: Untrusted code, plugins, third-party services

Combine with resource limits:

[service.untrusted]
isolation = "sandbox"
memory_limit_mb = 128 # Can't eat all RAM
cpu_shares = 50 # Can't hog CPU

Rough numbers on a modern Linux machine:

OperationProcessNamespaceSandboxNotes
Spawn5ms8ms50msSandbox is slower
First request1ms1ms2msCold start penalty minimal
Request throughput100k/s100k/s50k/sSandbox adds ~50% overhead
Memory10MB10MB30MBSandbox adds ~20MB

For most workloads, namespace isolation is the sweet spot: nearly native performance with good security.