Skip to content

Configuration reference ​

The whole cluster shares one JSON configuration file, deployed to /etc/opendsec/opendsec.json on every node. Every component reads it and uses the parts that concern it. This page describes each field and its default.

  • A complete production example: deploy/prod/cluster.example.json; the config a single-node install generates is /root/opendsec-ops/opendsec.json.
  • Durations are in seconds (fractions allowed).
  • For most numeric fields, 0 or leaving the field out means the default; some fields take -1 to turn something off, as noted.
  • Changing the config: on a cluster run by fleet, opendsecctl fleet config put and fleet rollout create -config-gen <generation> (operations manual §6.2); before fleet, opendsecops push-config all and opendsecops restart.
  • Validating a config: opendsecctl pki init -config <file> -out $(mktemp -d) succeeds only if its format and constraints are right.

Top level ​

json
{
  "tls":            { ... },   // mTLS between components, TLS on the public API
  "service_token":  "...",     // development only: component auth without TLS
  "iam":            { ... },
  "watcher":        { ... },
  "apiserver":      [ ... ],
  "placement":      { ... },
  "admission":      { ... },
  "image_store":    { ... },
  "network":        { ... },
  "edges":          [ ... ],   // edges listed one by one
  "edge_template":  { ... },   // every other edge (new machines, Kubernetes)
  "fleet":          { ... },
  "e2b":            { ... },
  "extensions":     { ... }    // extensions in the build (none in the open-source build)
}

At least one of tls.dir and service_token must be set; production clusters use tls.

tls ​

FieldMeaning
dirCertificate directory, e.g. /etc/opendsec/pki, holding ca.crt and each component's <role>-<id>.crt and .key (from opendsecctl pki init or opendsecops certs). Set, components use mTLS among themselves and the public API uses TLS

service_token ​

For development clusters: a shared secret components authenticate with when there is no TLS. Do not set it in production; a config delivered through fleet may not contain it.

iam ​

FieldDefaultMeaning
replicas—Raft replicas, each {"id", "addr", "raft_addr", "data_dir"}. With replicas, addr and state are not used, and admin_token or admin_token_file is required
replicas[].id—Replica id: unique, without /, . or spaces
replicas[].addr—gRPC address, e.g. 10.0.0.1:7001
replicas[].raft_addr—Raft address, e.g. 10.0.0.1:7011
replicas[].data_dir—Raft log and snapshots, e.g. /var/lib/opendsec/iam, on a local SSD
addr—Address of a single unreplicated IAM
statein memoryState file of a single IAM; unset, state lives in memory only and is lost on restart
admin_idadminThe administrator created at bootstrap
admin_token—The administrator's token, dsk.<admin_id>.<secret>. Placeholders containing change-me are refused
admin_token_file—Read admin_token from this file instead, so the config holds no secret. Only IAM nodes need the file
root_quotaunlimitedThe root project's quota, e.g. {"sandboxes": 10000}
max_token_ttl_s0 (none)Longest lifetime of issued tokens. With a limit, tokens that never expire cannot be issued

The admin token is used only when IAM first starts (bootstraps): it creates the admin_id principal with every permission on root.

watcher ​

FieldDefaultMeaning
replicas—Several watchers, each {"id", "addr"}. All probe edges and serve snapshots; the one holding the IAM lock reconciles
addr—Address of a single watcher
probe_interval1How often each edge is probed
probe_timeout0.5Timeout of one probe
unhealthy_after3Consecutive failures before an edge counts as unhealthy
reconcile_interval30How often quotas are reconciled with IAM
reconcile_min_age60Leases younger than this are never released by reconciliation
lost_edge_grace120After an edge is unhealthy this long, its sandboxes' quota is released
unbound_max_age600Leases no edge claimed are released after this long
lock_ttl10Lease time of the reconciliation lock

apiserver ​

An array with one entry per apiserver instance; opendsec-apiserver -index N runs entry N.

FieldDefaultMeaning
addr—Listen address, e.g. 10.0.0.1:7100
audit_diroffAudit log directory, e.g. /var/log/opendsec/audit
audit_max_mb256Rotate an audit log file at this size
audit_keep8Files kept, the current one included; the oldest go first
audit_runtimefalseAlso audit runtime calls (commands, files, HTTP, processes, ports) and read-only queries (getting and listing sandboxes, cluster status, projects, usage, token lists). By default every other call is audited: creation, fork, stop, pause, resume, network changes, image publishing and deletion, projects, principals, grants, tokens, cluster operations. Commands are recorded as their length and SHA-256 only, never their text
snapshot_refresh1How often the cluster snapshot is refreshed from the watchers

placement ​

Scheduling parameters, applied by each apiserver.

FieldDefaultMeaning
k2power-of-k: pick k random candidate nodes and use the least loaded
max_attempts3Tries on other nodes after an edge rejects a creation
reject_penalty2A node that rejected is ranked down for this long
overlay_grace2A sandbox just placed counts toward its node's load for this long, until the snapshot shows it
usage_floor0.2A node's actual CPU and memory use below this share does not count as load (the host's own baseline); -1 counts it always
burst_threshold0.8Only for burst_eligible_images on the container backend: once local (onprem) nodes are used beyond this share, sandboxes go to zone: cloud nodes first, with a probability growing with the excess; below it, cloud nodes are only a fallback when no local node fits. ≤0 disables
burst_eligible_images—Images that may run on cloud nodes (their layers are mirrored there). Other images and backends never go to cloud nodes

admission ​

Creation policy each apiserver enforces. Timeouts: 0 means the built-in default, -1 means none. Rates are per principal and per apiserver instance (N apiservers admit up to N times the rate); -1 disables a limit.

FieldDefaultMeaning
default_ttl_running_stop_s3600Idle timeout when a request sets none
default_ttl_max_s86400Lifetime when a request sets none
max_ttl_max_s604800 (7 days)Longest lifetime a request may ask for; more is invalid
request_rate10000Calls per second per principal (commands and file calls included)
request_burst2 × request_rateBurst
create_rate2000Sandbox creations per second per principal
create_burst32768Burst: a whole batch may start at once
auth_fail_rate5Failed authentications per second per client address; beyond it, new tokens from that address are not checked for a while
max_inflight_creates2048Concurrent creations per apiserver

A single creation over the rate gets rate_limited; batch creations are queued at the rate by the server and never partly fail on it. With the apiservers behind a load balancer, have it keep the client's source address, or every failed authentication counts against the load balancer's address.

image_store ​

FieldMeaning
rootMount point of the shared image store, e.g. /mnt/opendsec-images, the same path on every apiserver and edge node. Without it, only edge-local images (edges[].images) exist

File system requirements: Images and snapshots §5.

network ​

The cluster-wide network policy.

FieldMeaning
rule_setsRule sets: name → destinations (domains, IPv4 addresses or networks, optionally with :port[/protocol]). E.g. {"pypi": ["pypi.org", "files.pythonhosted.org"], "github": ["github.com", "codeload.github.com", "objects.githubusercontent.com"]}
default_rulesRules every sandbox has, under its own network_rules. {"*": true} lets every sandbox reach the internet by default; {"pypi": true} only PyPI
upstream_dnsResolvers the edges' DNS proxies use; default: the host's /etc/resolv.conf

Rule syntax and behaviour: Network policy.

edges and edge_template ​

edges lists edges one by one; edge_template applies to any edge not listed (opendsec-edge -id <id> uses the template when no entry has that id). New machines usually need no entry in edges.

FieldDefaultMeaning
id—Edge id, [a-z0-9-], 1 to 32 characters; required in edges
addr—Listen address, e.g. 0.0.0.0:7201
advertisethe bound addressAddress registered with the watchers
zoneonpremonprem or cloud, nothing else. cloud nodes are used only past burst_threshold
capacityautomatic{"cpu_cores", "memory_mb", "gpus", "disk_mb"}. With a real backend: with neither cpu_cores nor memory_mb set, they are the host's CPU count and total memory (setting only one leaves the other unset); without disk_mb, it is the size of the file system holding state_dir. Overcommit applies on top
cpu_overcommit8CPU overcommit factor
mem_overcommit1Memory overcommit factor
disk_overcommit1Writable-disk overcommit factor
mem_reserve_mbautomaticReject creations while the host's MemAvailable is below this. 0 means automatic (10% of RAM, at least 128 MB); -1 disables
disk_reserve_mbautomaticReject creations while the state directory's file system has less free space. 0 means automatic (5% of the file system, at least 1024 MB); -1 disables
cpu_pressure_limit0 (off)Reject creations while CPU pressure (/proc/pressure/cpu some avg10, percent) is above this. A burst of creations raises it too, so set it from the node's own measurements, e.g. 50
paused_mem_charge1Share of their memory that paused sandboxes whose pause frees memory (microVMs; containers with pause_reclaim) still count for admission, 0 to 1
persist_stateon with a real backendWrite sandbox records to disk and re-adopt the sandboxes after an edge restart
keep_sandboxes_on_exitfalseLeave sandboxes running when the edge exits. Needed for rolling upgrades
stopped_retention600How long records of ended sandboxes are kept
state_dir/var/lib/opendsecSandbox disks, snapshots and records; should be local NVMe. XFS allows reflink copies
agent_dirnext to the edge binaryDirectory of opendsec-aether and opendsec-chronus
images—Local static images: name → overlay lower directories (topmost first). Not needed with an image store
networkoffSandbox networking; below
gpu—GPU devices; below
backends{"sim": {...}}The backends this edge offers; below

edges[].network ​

FieldDefaultMeaning
enabledfalseEnable sandbox networking. Off, sandboxes only have a loopback interface
subnet10.88.0.0/16Sandbox address pool, a /30 per sandbox
dns_port15353The edge's DNS proxy port on the host

Edges sharing a machine need distinct subnets and dns_ports.

edges[].gpu ​

FieldMeaning
devicesGPU device nodes, e.g. ["/dev/nvidia0", "/dev/nvidia1"]. capacity.gpus defaults to their number
extraOther devices every GPU sandbox needs, e.g. /dev/nvidiactl, /dev/nvidia-uvm
mountsHost files or directories bound read-only into the sandbox (driver libraries, nvidia-smi)
shared_slotsSandboxes per device with gpu_mode: shared, default 4
pciPCI devices passed through to fullvm sandboxes with VFIO, e.g. ["0000:3b:00.0"]

edges[].backends ​

Backend name → backend config. The name is what creations put in backend, e.g.:

json
"backends": {
  "microvm":   {"runtime": "firecracker", "max_sandboxes": 4000, "default_disk_mb": 2048,
                "kernel": "/opt/opendsec/current/vm/vmlinux-6.1.167-opendsec",
                "firecracker": "/opt/opendsec/current/bin/firecracker",
                "jailer": "/opt/opendsec/current/bin/jailer",
                "image_pmem": true, "erofs_images": true, "free_page_reporting": true, "cold_age_s": 120},
  "container": {"runtime": "linux", "max_sandboxes": 8000, "default_disk_mb": 1024},
  "fncall":    {"runtime": "linux", "max_sandboxes": 4000, "default_disk_mb": 1024,
                "warm_pool": {"root/py:3.12": 64}}
}

Common fields:

FieldDefaultMeaning
runtimesimlinux (containers), firecracker (microVMs), qemu (full VMs), sim (simulated)
max_sandboxes—Most sandboxes of this backend running at once on the node
default_disk_mb10240Writable disk size when a request sets no disk_mb
disk_iounlimitedPer-sandbox disk I/O limits: {"read_mbps", "write_mbps", "read_iops", "write_iops"}, 0 for unlimited
core_schedon if the kernel supports itA core scheduling cookie per sandbox: two sandboxes never run on the two hardware threads of one core at the same time, which closes SMT side channels and keeps best-effort load off a latency-sensitive sandbox's core. true requires kernel support; false turns it off

linux runtime (container, fncall):

FieldDefaultMeaning
pids_max4096Process limit per sandbox
pause_reclaimfalseA pause pushes the container's memory to swap, read back ahead of use on resume. Needs host swap and vm.swappiness above 0
warm_pool—Image → containers kept started ahead of requests (the fncall backend). A request without snapshot, user, working directory, path restrictions or GPUs, and with the default disk, takes one and is ready in milliseconds
layer_cachetrueContainers run on node-local copies of their image layers, copied on first use, so running containers are unaffected when the image store (NFS) is unreachable

firecracker runtime (microvm):

FieldDefaultMeaning
kernelrequiredGuest kernel (vmlinux) path
firecracker, jailerthe programs of that name on PATHPaths of Firecracker and jailer
uid_base1000000000Each VM runs as its own uid from [uid_base, uid_base+65536)
vm_overhead_mb64Added to the guest memory for the VM's cgroup limit (the VMM itself, page tables, device buffers)
erofs_imagesfalseImages become EROFS disks built once in the image store and copied to each node, instead of flattened to ext4 on every node. Needs a guest kernel with EROFS and mkfs.erofs (erofs-utils) on the node
image_pmemfalseAttach the image as virtio-pmem with DAX: VMs of one image share the host page cache
free_page_reportingfalseMemory the guest frees goes back to the host (virtio-balloon)
cold_age_s0 (off)Above 0, DAMON in the guest pages out memory idle this long to compressed zram
zram_pct100zram size, percent of the VM's memory (0 to 400)
cold_boot_dirty_trackingtrueCold-booted VMs track dirty pages, so a pause writes only pages they touched. false: faster memory writes (host huge pages), but the first pause or snapshot writes all memory

image_pmem, free_page_reporting and cold_age_s need OpenDsec's guest kernel (deploy/guest-kernel, published as a GitHub release).

qemu runtime (fullvm):

FieldDefaultMeaning
qemuqemu-system-x86_64 on PATHQEMU path
kernel—Guest kernel
full_initfalseHand PID 1 to the image's own /sbin/init (aether runs beside it)

sim runtime: create_latency_ms (median creation time, lognormal), latency_sigma, failure_rate, for scheduling benchmarks.

fleet ​

The cluster management service; optional.

FieldDefaultMeaning
replicas—Each {"id", "addr", "data_dir"}; addr e.g. 10.0.0.1:7300, data_dir default /var/lib/opendsec/fleet
heartbeat_s10Node agent report interval
lock_ttl10Lease time of the leader lock
max_downloads64Concurrent release downloads served per replica
keep_releases10Releases kept besides those in use
edge_port7201Port edges listen on at the node's private address
ready_portsiam 9461, watcher 9462, apiserver 9463, edge 9464, fleet 9465, e2b 9467Each role's /readyz port, which node agents check after restarting a unit
prometheus_url—Prometheus for rollout gates
gates—Rollout gates: [{"name", "query", "max"}]. While a batch bakes, each PromQL query must stay at or below max, or the rollout pauses
json
"fleet": {
  "replicas": [{"id": "0", "addr": "10.0.0.1:7300"}, {"id": "1", "addr": "10.0.0.2:7300"}, {"id": "2", "addr": "10.0.0.3:7300"}],
  "heartbeat_s": 10,
  "prometheus_url": "http://10.0.0.3:9090",
  "gates": [{"name": "create errors", "query": "sum(rate(opendsec_apiserver_create_seconds_count{reason!=\"ok\"}[5m]))", "max": 1}]
}

e2b ​

The E2B-compatible gateway; optional.

FieldDefaultMeaning
gateways—Gateway instances: [{"addr", "tls_cert", "tls_key"}]. addr e.g. 10.0.0.1:7400; set both tls_cert and tls_key for the gateway to serve HTTPS itself, or neither for plain HTTP behind a TLS-terminating load balancer
projectrequired with gatewaysSandboxes are created in this project. A request can name another with the X-OpenDsec-Project header
domain—The sandboxes' domain returned to SDKs, which then reach <port>-<id>.<domain> (needs wildcard DNS and a certificate pointing at the gateways). Unset, clients set E2B_SANDBOX_URL
templates—E2B template id → sandbox. SDKs default to base. A template id containing / is taken as an image name with default_template's other settings
default_template—As above, the defaults
max_timeout_s0 (admission cap only)Cap on a sandbox's timeout

Template fields:

FieldDefaultMeaning
imagerequiredImage
backendmicrovmBackend
cpu2CPU cores
memory_mb512Memory. Must equal the snapshot's when the image is a memory snapshot
disk_mbthe backend'sWritable disk
start_cmd—Started with bash -l -c in every new sandbox (an E2B template's start command)
ready_port—Wait for this port to listen before handing the sandbox out
ready_timeout_s60Longest wait

More in Migrating from E2B.

Command-line flags of the components ​

Usually passed by the systemd units (deploy/systemd/); you rarely set them yourself.

ProgramFlags
opendsec-iam-config, -replica <id> (which replica to run, replicated mode), -join (a new replica: do not bootstrap; wait for opendsecctl iam member add), -metrics, -log
opendsec-watcher-config, -replica <id>, -metrics, -log
opendsec-apiserver-config, -index <N> (run entry N of apiserver), -drain-delay (on SIGTERM, fail /readyz and keep serving this long, default 6s), -grace (then give calls in flight this long, default 30s), -metrics, -pprof, -log
opendsec-edge-config, -id <id>[,<id>...] (all for every configured edge; unlisted ids use edge_template), -addr, -advertise, -metrics, -pprof, -log
opendsec-fleet-config, -replica <id>, -metrics, -log
opendsec-e2b-config, -index <N>, -ca (default tls.dir/ca.crt), -drain-delay (default 5s), -metrics, -log
opendsec-nodeagent-config (default /etc/opendsec/opendsec.json), -name, -addr, -fleet, -root (default /opt/opendsec), -check, -metrics, -log

-metrics <address> serves /metrics, /healthz and /readyz. Default ports per role: Monitoring and alerts.

Implements the paper DeepSeek Elastic Compute (DSec) (arXiv:2609.22978). An independent project, not affiliated with DeepSeek.