Appearance
hopboxd configuration
hopboxd is the hopbox daemon: ssh box@host spawns a box (a Firecracker microVM or a Docker container) and bridges your session into it, and it serves the AI-control MCP plane. Run hopboxd --help for the authoritative flag list.
Config file
Instead of a wall of flags, point hopboxd at a YAML file:
sh
hopboxd --config /etc/hopbox/hopboxd.yamlEach key is the flag name without the --; its value becomes that flag's default, and an explicit --flag on the command line still overrides it. Unknown keys are rejected (so typos surface). See deploy/hopboxd.example.yaml in the repo.
yaml
ssh-addr: ":22"
compute: microvm
idle-reap: 3h
verify-mode: github
github-client-id: "Ov23li…"
quota-boxes: 3
quota-boxes-verified: 20Front door (SSH)
The SSH listener where the username is a box spec and the client key is the identity. See the username grammar.
| Flag | Default | Description |
|---|---|---|
--ssh-addr | :2222 | Front-door SSH listen address (username = box spec, key = identity). |
--host-key | ./hopboxd-ssh-host-key | Front-door SSH host key path (auto-created on first run). |
--admin-keys | (empty = no admins) | An authorized_keys file; those keys may open the fleet-wide admin console via ssh sudo@host. |
--default-image | alpine | Image used when the username names none. With microVM, set this to a catalog image (e.g. ubuntu-22.04). |
--default-cpus | 2 | CPU cap (vCPU) per box. 0 = unlimited. A named flavor in the spec overrides this. |
--default-mem-mb | 2048 | Memory cap (MB) per box. 0 = unlimited. A named flavor in the spec overrides this. |
Accounts & verification
An account is an SSH key fingerprint. ssh cli@host acc shows it; acc register runs identity verification (when enabled) to unlock durable features. See accounts.
| Flag | Default | Description |
|---|---|---|
--verify-mode | off | Verification for acc register: off | github | manual. |
--github-client-id | (empty) | GitHub OAuth app client id (device flow; no secret needed) for github mode. |
--quota-boxes | 0 | Max concurrent boxes per key (0 = unlimited) — the per-key floor against key-spam. |
--quota-boxes-verified | 0 | Max concurrent boxes per verified account (0 = unlimited). |
--verify-addr | (empty) | Serve the verification callback endpoint here (host:port), behind a public TLS proxy. |
--verify-base-url | (empty) | Public base URL the QR / webhook targets, e.g. https://box.hopbox.dev. |
--verify-secret | (host key) | HMAC secret for manual-mode completion tokens. |
Lifecycle
Boxes auto-suspend when idle (memory snapshot to disk; resume on reconnect) and are idle-reaped if never reconnected to. Browser-terminal boxes are the exception — ephemeral, reaped a short --grace after the socket closes. See lifecycle.
| Flag | Default | Description |
|---|---|---|
--idle-timeout | 2m | Suspend a box after this long empty and quiet — no session attached, no interactive shell open in the box, and load below the threshold (memory snapshot to disk; resumes on reconnect, and the HTTP proxy resumes it on request). A box can override it with box-guest idle. |
--idle-reap | 3h | Reap an auto-suspended box left inactive this long (never reconnected). 0 = never. A durable box (box-guest durable on), a keep-alive pin, or box-guest auto-suspend off exempts a box. |
--grace | 2m | Reconnect window for a run box before it is reaped after its owner detaches. 0 = reap immediately. |
On a clean stop (SIGTERM) the daemon also drains: every running, persistent box is suspended, concurrently, so the next start resumes it, and any attached SSH session is warned first. These are internal constants, not flags — 4 boxes snapshotted at once, 60s timeout per box, 3s warning grace before suspending. See shutdown drain and the systemd unit.
Compute backend
| Flag | Default | Description |
|---|---|---|
--compute | docker | Compute backend: docker | microvm. |
--agent-bin | (empty) | Docker: host path of the Linux hopbox-agent binary side-loaded into each box. (microVM bakes the agent into the rootfs.) |
--guest-bin | (empty) | Docker: host path of the Linux box-guest binary side-loaded into each box. (microVM bakes it in.) |
microVM (Firecracker)
Active when --compute microvm. A box is a Firecracker microVM booted from a CoW clone of a catalog image. See microVM & the image catalog.
| Flag | Default | Description |
|---|---|---|
--fc-bin | /usr/local/bin/firecracker | Firecracker binary. |
--fc-kernel | /opt/hopbox-microvm/vmlinux | vmlinux guest kernel. |
--fc-images-dir | /opt/hopbox-microvm/images | Base-image catalog dir; image <name> → <dir>/<name>.ext4. Built with build/microvm/build-rootfs.sh / build-deboot.sh. |
--fc-rundir | /var/lib/hopbox/microvm | Per-VM working dir (CoW disks, sockets). |
--fc-bridge | (empty → hopbox-vmnet) | Host bridge for the microVM fleet. Set with --fc-subnet to run a second fleet beside another daemon. |
--fc-subnet | (empty → 10.0.0) | /24 base — first three octets. The bridge gateway is .1; boxes reach the host (agent hub + metadata) there. |
Owner network
By default every box shares one open segment on the microVM bridge — any box can reach any other. --owner-network isolates boxes per owner and gives them DNS names. See Owner network.
| Flag | Default | Description |
|---|---|---|
--owner-network | false | Isolate boxes by owner on the microVM bridge (nftables): a box reaches only its own owner's boxes and the gateway; cross-owner box↔box is dropped. Also serves owner-scoped <name>.hbox names on the gateway resolver. Fail-closed — the daemon refuses to start if the nft ruleset can't be programmed (nft missing). microVM backend only. |
Storage
Persistent per-box home volumes (microVM): each named box gets an ext4 image at /home/dev that survives the box. See persistent home.
| Flag | Default | Description |
|---|---|---|
--homes-dir | (empty = off) | Directory for persistent home volumes (ext4), keyed by owner + box name. Empty = ephemeral rootfs only. |
--homes-gc-ttl | 48h | Reclaim orphan home volumes (no live box row) older than this, on startup and hourly. 0 = off. Homes of live boxes are never touched. |
--home-size-mb | 2048 | Size of each home volume (MB). |
Workspace
The cross-box shared /wrk — a JuiceFS filesystem over S3-compatible storage, one volume per owner, isolated by Postgres schema. Off unless --workspace-pg-dsn is set. Stand up the backend with deploy/workspace-setup.sh (it prints these) and note it needs a FUSE guest kernel.
| Flag | Default | Description |
|---|---|---|
--workspace-pg-dsn | (empty = off) | Admin Postgres DSN for workspace metadata (e.g. postgres://hopbox:PASS@127.0.0.1/hopbox_ws?sslmode=disable). Enables the feature. |
--workspace-s3-endpoint | (empty) | Box-reachable object-store endpoint (e.g. http://10.0.0.1:9000). |
--workspace-s3-bucket | hopbox-wrk | Object-store bucket holding workspace data. |
--workspace-s3-key / --workspace-s3-secret | (empty) | Object-store credentials (baked into each volume's metadata at format). |
--workspace-secret | (host key) | Server secret deriving per-owner role passwords; empty = derived from the SSH host key. |
--workspace-automount | false | Auto-mount /wrk in every (non-browser) box on startup. Off = boxes mount explicitly — recommended on a public front door, so visitors don't each accrue a volume. |
Secrets
| Flag | Default | Description |
|---|---|---|
--secrets-key | (host key) | Server secret that encrypts stored secrets at rest (AES-256-GCM); empty = derived from the SSH host key. Keep it stable — rotating it makes existing secrets undecryptable. |
Agent hub & metadata
| Flag | Default | Description |
|---|---|---|
--agent-listen | :7777 | Address the in-box agent dials back on (reverse tunnel). |
--advertise | (empty) | Address the in-box agent is told to dial. Empty = derived from the backend gateway (host.docker.internal for docker, the bridge .1 for microVM) + the --agent-listen port. |
--meta-addr | :8090 | Box metadata API listen address. Boxes reach it by source IP — this powers box-guest. |
--db | ./hopboxd.db | Box database path (SQLite). Tracks box ownership, state, and durable-home mapping across restarts. |
This path (plus the derived -accounts.db / -shares.db) and --host-key above are exactly what hopbox-backup snapshots and ships off-host — see Backups & restore for the daily timer, what's in an archive, and how to restore.
AI-control plane
Serve the MCP plane and the canvas surfaces. The installer defaults --mcp-addr to unix:/run/hopboxd-mcp.sock.
| Flag | Default | Description |
|---|---|---|
--mcp-addr | (empty = off) | Serve the AI-control MCP plane here (unix:/path or host:port). |
--surface-addr | (empty = off) | Serve AI-rendered canvas surfaces over HTTP here (host:port). |
--surface-url | (empty) | Public base URL for surface links (default: http://<surface-addr>). |
HTTP API
Serve the HTTP API — exec, list, and lifecycle over REST, authed by hbx_ keys minted with ssh cli@host apikeys. Plain HTTP; front it with your own TLS proxy (same posture as --verify-addr).
| Flag | Default | Description |
|---|---|---|
--api-addr | (empty = off) | Serve the HTTP API here (host:port). GET /healthz is unauthenticated; everything else needs a key. |
--preview-domain | (empty = off) | Serve box ports at <box>-<port>.<domain> as well as the /v1/boxes/{box}/proxy/{port}/ path form. On a subdomain the app owns the origin root, so root-absolute asset URLs work — see preview subdomains. Needs a wildcard DNS record and a certificate per host (Caddy on_demand + ask http://<api-addr>/v1/tls-ask). |
--wrk-mount-root | /var/lib/hopbox/wrk-http | Host directory where the daemon mounts each owner's workspace to serve /v1/wrk/*. Only used when --api-addr and --workspace-pg-dsn are both set. |
Diagnostics
| Flag | Default | Description |
|---|---|---|
--check | false | Validate the config file + flags and exit (no listeners, no engine). config OK + exit 0, or the errors + a non-zero exit. Used by hopbox-host update to gate a deploy before touching the running daemon. |
--health | false | Probe the running daemon's front door (SSH banner) and exit 0 if healthy, non-zero otherwise — the cheap liveness poll hopbox-host polls after a restart. It answers "is the daemon alive?", not "can it do its job?" — that's --health-full below. Stays a bool (there's no --health=full): the installed hopbox-host isn't part of the release a deploy swaps, so it always invokes plain --health. |
--health-full | false | DEEPLY probe the running daemon: spawn a throwaway box through its own front door with a freshly generated identity key, run a command in it and match a nonce in the output, tear it down, and check the image catalog + workspace object store. Prints a per-check breakdown and exits 0/1 — hopbox-host update's deploy commit gate. See the deep probe below. |
--health-timeout | 90s | Overall budget for --health-full, so a wedged backend fails the gate instead of hanging the deploy. A hard stop fires ~20s past the budget if a check is wedged somewhere the deadline can't reach. |
The deep probe (--health-full)
--health only proves the front door is answering — true of essentially any daemon that got as far as binding its port. --health-full proves the daemon can do its job, over the same surfaces a user gets, in order:
| Check | Proves | Skipped when |
|---|---|---|
front-door | The SSH banner answers (same probe as --health). | never |
image-catalog | ssh images@host lists a catalog and it contains --default-image. | the compute backend isn't microvm — only the microVM backend advertises a catalog; box-spawn covers image resolution on docker |
box-spawn | Spawns a throwaway box named anon-hlth-<rand>, runs a command in it, and matches a nonce in the output — proving a command actually ran, not just that a channel opened. The anon- prefix means the box gets no persistent home volume. | never — this is the check the probe exists for |
workspace-s3 | --workspace-s3-endpoint answers (MinIO's /minio/health/live first, then a plain GET). | --workspace-pg-dsn is empty (/wrk is off). Fails — does not skip — if /wrk is on but the endpoint is empty. |
cleanup | Removes every box the probe's ephemeral identity owns. Runs on every path, including a failed spawn or an exhausted budget, so a probe run never leaks a box. | never; a cleanup failure fails the probe |
Skipped checks do not fail the probe. Run it by hand against a live daemon:
sh
hopboxd --config /etc/hopbox/hopboxd.yaml --health-fullhopboxd deep health probe (compute=docker, budget=1m30s)
front-door ok 1ms SSH banner on 127.0.0.1:2299
image-catalog skip the docker backend advertises no catalog; box-spawn covers image resolution
box-spawn ok 1.219s spawned anon-hlth-8aa29d8e, ran a command in it
workspace-s3 skip /wrk is not configured
cleanup ok 6ms removed anon-hlth-8aa29d8e
healthy: 3 checks passed, 2 skippedA failed check fails the whole probe (exit 1):
box-spawn FAIL 208ms box anon-hlth-46d7b75d: hopbox: box failed to start: compute: docker: create: ...
unhealthy: 1 of 3 checks failed, 2 skippedhopbox-host update runs this once, right before it commits the deploy — see Updating. --health-full and --health-timeout are operational hooks like --check/--health: they aren't config file keys.
See also
- Boxes over SSH — the concepts and a walkthrough.
- SSH & the front door — the username grammar and the catalog.
- The AI-control plane — MCP resources, tools, the canvas loop.
hopbox-hostreference — the deploy/update tool that drives--check,--health, and--health-full.- Backups & restore — the daily off-host backup of
--dband--host-key.