tabbify.io
Documentation
Product directionEDGE BOX
ReferenceArchitecture

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.