The Runtime
When you deploy an app, the selected supervisor starts one detached
tabbify-runner. The runner joins the private mesh at the
application's deterministic address and boots the OCI image inside a CPU-only
Firecracker microVM.
Supervisor and runners
supervisord hosts nothing itself. It is pure control plane: it spawns runners, persists their records to disk, monitors liveness, and re-adopts them on restart. Each runner hosts one app and runs it as a Firecracker microVM — Tabbify ships exactly one runtime — serving on [app_ula]:8730.
# Orchestrator; --no-mesh binds loopback for local dev (no root/TUN)
supervisord --runner-bin ./tabbify-runner --no-mesh
# A runner is normally spawned for you, derived from the app UUID
tabbify-runner --uuid 0191e7c2-1111-7222-8333-444455556666 --no-mesh
A supervisor advertises only what it can run: it joins with tag [supervisor] and adds firecracker if /dev/kvm is present. (It may also carry a docker tag when a Docker daemon is reachable and a builder tag when it's designated a build host — these are build-backend capabilities used by the deploy pipeline, not user-selectable runtimes. The app runtime is always Firecracker.)
Lifecycle
The supervisor has internal lifecycle operations, and selected lifecycle actions are exposed through the node:
POST /v1/apps/{uuid}/start spawn runner (idempotent if alive), wait healthy 30s, return app_ula
POST /v1/apps/{uuid}/stop shutdown runner, forget record, keep cached artifacts
POST /v1/apps/{uuid}/purge shutdown + clear cached OCI-image artifacts (rootfs)
GET /v1/apps list runner records on disk + a live health probe each
start replies with the deterministic address and restart state:
{"state":"running","app_ula":"fd5a:1f02:44a5:240b:121a::1","restart_status":"running","restart_count":0}
Each runner record stores its UUID, process and control-socket details, address, and restart state on the selected host. This supports local reconciliation after the supervisor process restarts.
Health and local recovery
Runners expose a Unix-socket control channel (JSON-lines, one command per connection):
-> {"cmd":"health"}
<- {"reply":"health","state":"running","app_ula":"fd5a:1f02:…::1","pid":12345,"app_health":"serving"}
Because runners are detached, a supervisor-process restart can re-adopt a living local runner. If the recorded process is dead or unresponsive, the supervisor uses its local restart policy. This is recovery on one host, not workload redundancy or automatic relocation to another host.
Restart supervision
App-level (L2) restart is the supervisor's job: per-runner exponential backoff (base 10s, cap 300s, doubling per failure, reset after 60s stable) with a crash-loop threshold of 5 failures. Keeping supervisord itself alive (L1) belongs to the launch backend — a container --restart policy or systemd.
docker run -d --restart on-failure --device /dev/net/tun --cap-add NET_ADMIN \
-v tbf-state:/var/lib/tabbify tabbify-supervisor
Deploy health and current evidence
A deployment candidate must pass the configured health gate before activation. The deploy status endpoint reports the current build or activation stage, and lifecycle operations report the current runner state. These are current-state views rather than a durable release timeline.
When available, deploy status includes a bounded tail of application output kept on the selected host. Use it for startup diagnosis, but do not treat it as a central log archive. Host loss or local retention can remove older output.
The exact public lifecycle and status operations evolve independently across the
web console, REST, CLI, and MCP. Use /openapi.json, tcli --help, and MCP tool
discovery for the operations currently exposed by each interface.