Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/deeplethe/forkd/llms.txt

Use this file to discover all available pages before exploring further.

The forkd-controller daemon is the long-lived process that owns everything in a production forkd deployment: the on-disk snapshot registry, all active child Firecracker processes, cgroup quota enforcement, the REST API, and the append-only JSON audit log. Clients — the CLI, Python SDK, TypeScript SDK, and MCP server — all talk to it over HTTP/JSON, loopback by default. Running the daemon is recommended for any deployment beyond local development.

Install with systemd

The repository ships a hardened systemd unit at packaging/systemd/forkd-controller.service. Install it, create the token file, and start the service:
sudo install -m 0644 packaging/systemd/forkd-controller.service /etc/systemd/system/
sudo mkdir -p /etc/forkd
sudo bash -c 'head -c 32 /dev/urandom | base64 > /etc/forkd/token'
sudo chmod 600 /etc/forkd/token
sudo systemctl enable --now forkd-controller
Verify the daemon is up:
curl http://127.0.0.1:8889/healthz
# {"ok":true}
The /healthz endpoint is always unauthenticated so that load balancers and Kubernetes probes can reach it without credentials.

Authentication

The daemon uses bearer-token authentication. When --token-file is supplied, every request except /healthz must include a matching Authorization: Bearer header. Without --token-file, the daemon runs unauthenticated — safe only when bound to loopback.

Passing the token in requests

TOKEN=$(sudo cat /etc/forkd/token)

# Spawn five sandboxes
curl -s \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -X POST http://127.0.0.1:8889/v1/sandboxes \
  -d '{"snapshot_tag":"pyagent","n":5,"per_child_netns":true,"memory_limit_mib":256}'

# List snapshots
curl -s -H "Authorization: Bearer $TOKEN" http://127.0.0.1:8889/v1/snapshots
SDKs read the token from the FORKD_TOKEN environment variable:
export FORKD_URL=http://127.0.0.1:8889
export FORKD_TOKEN=$(sudo cat /etc/forkd/token)

Token rotation

The token is read once at daemon startup. To rotate:
1

Write a new token

sudo bash -c 'head -c 32 /dev/urandom | base64 > /etc/forkd/token'
sudo chmod 600 /etc/forkd/token
2

Restart the daemon

sudo systemctl restart forkd-controller
Existing sandboxes survive the restart. In-flight HTTP requests do not.
3

Update clients

Distribute the new token value to any SDK clients or CI secrets that use FORKD_TOKEN.
Treat the bearer token like a root credential. Possessing it grants full control over all snapshots and sandboxes on the host. Store it at mode 0600, never log it, and rotate on any access change.

TLS

For any deployment where the daemon binds to a non-loopback address, enable TLS to protect the bearer token and API payloads in transit.
# Generate (or provision from Let's Encrypt / your internal CA):
sudo mkdir -p /etc/forkd/tls
# drop cert.pem + key.pem into /etc/forkd/tls/ with mode 0600

# Add to ExecStart in the systemd unit:
--tls-cert /etc/forkd/tls/cert.pem \
--tls-key  /etc/forkd/tls/key.pem
The daemon uses rustls 0.23 with the aws-lc-rs crypto provider. TLS 1.2 and TLS 1.3 are accepted; legacy cipher suites are not negotiable.
TLS does not automatically enable authentication. Supply --token-file as well — the two flags are independent.
To rotate TLS certificates, write new files to the same paths and run sudo systemctl restart forkd-controller.

Prometheus metrics

Scrape :8889/metrics from your Prometheus instance. No auth is required if the endpoint is loopback-only; with a non-loopback bind, the scrape request must carry the bearer token like any other request.
curl -s -H "Authorization: Bearer $TOKEN" http://127.0.0.1:8889/metrics
# forkd_build_info{version="0.5.2"} 1
# forkd_snapshots_total 3
# forkd_sandboxes_active 5

Stable metric names

MetricTypeDescription
forkd_build_infoGaugeAlways 1. Labels carry version. Use to detect daemon downtime.
forkd_snapshots_totalGaugeNumber of registered snapshots in the registry.
forkd_sandboxes_activeGaugeNumber of live child Firecracker processes.

Suggested alerts

# Daemon is down (no scrape for 1 minute)
- alert: ForkdDaemonDown
  expr: absent(forkd_build_info) == 1
  for: 1m
  annotations:
    summary: "forkd-controller is not reachable"

# Sandbox count approaching vCPU saturation
- alert: ForkdSandboxSaturation
  expr: forkd_sandboxes_active > (count(node_cpu_seconds_total{mode="idle"}) * 0.8)
  for: 5m
  annotations:
    summary: "forkd sandbox count > 80% of host vCPU count"

Audit log

Every request is recorded as a single JSON line in /var/log/forkd/audit.log:
{"ts":"2026-05-12T07:12:34Z","method":"POST","path":"/v1/sandboxes","status":201,"latency_us":98342,"ua":"forkd-cli/0.1"}
Rotate with logrotate. The daemon does not yet implement SIGHUP-triggered file re-open — after a rotation, run sudo systemctl restart forkd-controller.
Pipe the audit log into a SIEM or log aggregator (Vector, Fluent Bit, etc.) for real-time alerting on unexpected POST /v1/snapshots or high error rates.

Daemon CLI flags

The forkd-controller serve subcommand accepts the following flags. Every flag also reads from the corresponding environment variable.
FlagEnv varDefaultDescription
--bindFORKD_BIND127.0.0.1:8889TCP address the HTTP/HTTPS server listens on.
--stateFORKD_STATE/var/lib/forkd/state.jsonOn-disk JSON registry. Auto-created on first run.
--snapshot-rootFORKD_SNAPSHOT_ROOT(daemon default)Root directory for tagged snapshot subdirs (vmstate + memory.bin).
--audit-logFORKD_AUDIT_LOG/var/log/forkd/audit.logAppend-only JSON audit log path.
--token-fileFORKD_TOKEN_FILE(none — unauthenticated)File containing the bearer token. Required for any non-loopback deployment.
--tls-certFORKD_TLS_CERT(none — plain HTTP)PEM-encoded TLS server certificate chain.
--tls-keyFORKD_TLS_KEY(none — plain HTTP)PEM-encoded TLS private key matching --tls-cert.
--prewarm-scratch-dirFORKD_PREWARM_SCRATCH_DIR/dev/shm/forkd-prewarmScratch directory for prewarm throwaway snapshots. Use tmpfs.
--branch-concurrencyFORKD_BRANCH_CONCURRENCY4Maximum concurrent BRANCH operations. Each writes a full memory.bin (256 MiB – 8 GiB), so this bounds peak transient disk usage.

SDK environment variables

VariablePurpose
FORKD_URLBase URL for the controller (e.g. http://127.0.0.1:8889). Read by the Python SDK, TypeScript SDK, and CLI.
FORKD_TOKENBearer token passed as Authorization: Bearer. Read by all official clients.

systemd unit hardening

The shipped unit (packaging/systemd/forkd-controller.service) runs with a narrow capability set and systemd hardening options. Key settings:
CapabilityBoundingSet=CAP_NET_ADMIN CAP_SYS_ADMIN CAP_KILL CAP_SETUID CAP_SETGID
AmbientCapabilities=CAP_NET_ADMIN CAP_SYS_ADMIN CAP_KILL CAP_SETUID CAP_SETGID
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/var/lib/forkd /var/log/forkd /sys/fs/cgroup
RestrictNamespaces=net mnt user pid cgroup
SystemCallFilter=@system-service
SystemCallFilter=~@privileged @resources
The daemon requires CAP_NET_ADMIN (tap device creation, per-child netns) and CAP_SYS_ADMIN (cgroup v2 writes). MemoryDenyWriteExecute=false is required because Firecracker JIT-maps guest memory.

Build docs developers (and LLMs) love