Documentation
ReferenceArchitecture

The tabbify.toml manifest

tabbify.toml declares everything about an app in one file: how it builds, how it runs, and where it deploys. It sits at the root of your app directory next to your Dockerfile (or app.wasm). The wire struct is vendored identically across tcli, the supervisor, and the node, so the fields mean the same thing everywhere they are read.

The unified tabbify.toml is the target manifest format, and migration is in flight: today tcli push still primarily reads the legacy manifest.toml, the supervisor parses tabbify.toml, and the node reads it on a GitHub webhook. A legacy manifest.toml continues to work — see Gotchas for precedence.

A minimal manifest

The smallest manifest declares a name, a build kind, and a runtime:

[app]
name = "hello"

[build]
kind = "wasm"

[runtime]
type = "wasm-http"

Identity: [app]

[app] carries the app's identity. You only write name (and optionally description); the UUID is minted for you on first tcli push and tracked in tabbify.lock.

[app]
id          = "0191e7c2-1111-7222-8333-444455556666"  # stamped by tcli, do not hand-edit
name        = "booking-site"
description = "Hugo's barbershop booking"

The id is a UUID v7. tcli mints it once, writes it to tabbify.lock, and stamps it into the manifest body before uploading to S3. That UUID is also what deterministically derives the app's private mesh address — see Private Mesh.

Build: [build]

[build].kind is a frozen enum: docker or wasm. It selects the build artifact, independent of how the app later runs.

[build]
kind       = "docker"     # docker | wasm
context    = "."
dockerfile = "Dockerfile"
builder    = "ec2-prod"   # which supervisor builds the image

builder names the supervisor that compiles the image. There is no firecracker build kind — to run under Firecracker you build with kind = "docker" and override the runtime per target (below).

Runtime, ports, and lifecycle: [runtime]

[runtime].type is a frozen enum: docker, firecracker, or wasm-http. Crucially, runtime is chosen at deploy time, not baked at build time — the same Docker image can run as a container on one host and a Firecracker microVM on another.

[runtime]
type             = "docker"        # docker | firecracker | wasm-http
lifecycle        = "on_request"    # always_on | on_request
idle_timeout_sec = 300
memory_mb        = 512
vcpus            = 1

For WASM apps you also point at the entry component and set a fuel budget:

[runtime]
type             = "wasm-http"
entry            = "app.wasm"
fuel_per_request = 1000000000
memory_mb        = 64
  • lifecyclealways_on spawns at registration and stays up; on_request lazy-spawns on first request and idle-stops after idle_timeout_sec. An app started explicitly via the API is pinned and is never idle-reaped.
  • ports — apps don't pick a public port. A runner binds its app on [app_ula]:8730 over the mesh, and the node proxies /app/<uuid> to it. For Firecracker the guest listens internally on 8080 (SUPERVISOR_FC_APP_PORT). See Routing & public access.

Deploy targets: [[deploy]]

Each [[deploy]] block places the app on one supervisor and may override the runtime and inject env for that target only:

[[deploy]]
supervisor = "thinkpad"
runtime    = "firecracker"   # overrides [runtime].type for this target
[deploy.env]
LOG_LEVEL = "debug"

[[deploy]]
supervisor = "ec2-prod"
runtime    = "docker"

A supervisor only accepts a runtime it advertises. Supervisors announce capability tags — wasm-http always, docker if the daemon is reachable, firecracker if /dev/kvm is available — and the node validates the requested runtime against those tags before placement, rejecting unsupported combinations with a 400.

The multi-target deploy loop (per-target runtime override and [deploy.env] merge) is designed and partially wired; treat [[deploy]] as the intended shape while the node's deploy path is finished.

Secrets and capabilities

tabbify.toml does not carry plaintext secrets. Credentials live in the encrypted secret store and are injected as environment at runtime — never written into the manifest, the event log, or the prompt. Sandbox capability bounds (allowed hosts, event segments) are declared and enforced by the supervisor; see Services & capabilities.

Gotchas

  • tabbify.toml takes precedence over a legacy manifest.toml if both exist.
  • Unknown fields are silently ignored (#[serde(default)], no deny_unknown_fields), so a typo like lifcycle falls back to the default rather than erroring. Watch your spelling.
  • The S3 latest marker is the canonical version number; the version field in the manifest is display-only and ignored by the supervisor.

Once your manifest is ready, publish with tcli push and follow the deploy pipeline.