Architecture overview
Tabbify is two planes bolted together. The substrate is an event-sourced core: an append-only log is the source of truth, and services react to events rather than calling each other. The app-layer is what turns your code into a running service — a CLI, a registry, a public node, supervisors with per-app runners, the private mesh that wires them, and an auth service that signs who gets in. This page is the map; each subsystem has its own page linked below.
The substrate core
The spine is the transaction-node: an append-only event log where every state
change — app_registered, app_started, secret_registered — is recorded
before it takes effect. Recovery is replay; every mutation is auditable. The
read-node builds projections off that log; the api-gateway terminates external
webhooks and resolves secrets out of band; the original sandbox-supervisor
subscribes to sandbox_spawn_requested and emits sandbox_log / sandbox_started
back into the log. Nothing talks to a service directly — inbound traffic becomes an
event, your app subscribes to a segment, and emits its result. See
Core concepts and Services & capabilities.
Two honest caveats: the event stream is fire-and-forget (a late subscriber misses events; recent ones sit in a ring buffer), and the chat approval card is designed but not yet wired in the frontend.
The app-layer
A handful of pieces take a tabbify.toml to a reachable URL:
- tcli —
tcli deploy --remotehands your repo URL and commit to the node, which clones and builds it for you. Thetabbify-io/deploy@v1GitHub Action runs the same command on everygit push. (Local-build deploy is not implemented;--remoteis the path.) - registry — a mesh-only OCI registry (Zot behind a Rust wrapper), gated by tabbify auth, per-tenant namespaces. Built images land here, addressable only over the mesh.
- supervisor + runner — a supervisor is a universal node that both builds and runs apps (capability tags:
builder,firecracker). Its control plane spawns one detachedtabbify-runnerper app on[app_ula]:8730and re-adopts living runners after a crash. - runtimes — there is exactly one runtime: Firecracker, booted from your OCI image.
[build].kind = "docker"produces an image; the runner turns it into an ext4 rootfs and boots a microVM. (Legacy[runtime]/wasmfields parse for wire-compat but resolve to Firecracker — there is no per-app runtime choice.) - mesh — a userspace WireGuard overlay; every app gets a deterministic IPv6
fd5a:1f02:<blake3(uuid)>::1. - node — the public edge. It exposes REST + MCP and proxies
/app/<uuid>over the mesh. Users reach it over HTTPS athttps://api.tabbify.io(CloudFront); the node itself binds locally and is never addressed by raw IP or port. - auth — Ed25519 JWTs and node-join tokens; the coordinator trusts token claims, not a peer's self-asserted
--tag.
The data path
A request from the public internet to a running app:
browser
│ HTTPS
▼
api.tabbify.io (CloudFront edge)
│
▼
tabbify-node Bearer-auth, derive_app_ula(uuid)
│ WireGuard tunnel (mesh)
▼
tabbify-runner [fd5a:1f02:…::1]:8730
│
▼
Firecracker microVM (your OCI image's entrypoint)
The node derives fd5a:1f02:<blake3(uuid)[0:6]>::1 from the UUID and dials
[app_ula]:8730 directly — there is no supervisor query and no
uuid → supervisor cache. The mesh route is the discovery: when a supervisor
starts an app it advertises the app-ULA, and the node simply computes the same
address and dials it. Bodies stream both directions; an unreachable app is 502, a
bad UUID is 400. See Routing & public access.
Deploys flow the other way: git push → deploy pipeline →
the node clones and builds your repo on a builder supervisor → the image is pushed
to the mesh registry → a supervisor spawns the runner as a Firecracker microVM.
Start there with the Quickstart, or run your own edge via
self-hosting a node.
Honest scope
App-ULA direct binding is live: the node computes the app-ULA and dials it
directly, with no supervisor lookup. The unified tabbify.toml is the manifest
both tcli and the supervisor read; legacy [runtime] fields still parse but are
ignored (everything resolves to Firecracker). Node auth is a Bearer token issued to
you; its activation/version stores are still in-memory. NAT traversal goes direct →
hole-punch → DERP relay; the relay (Stage 3) is live, so peers behind symmetric
NATs reach the mesh through it.