tabbify.io
Documentation
Product directionEDGE BOX
ReferenceArchitecture

The tabbify.toml manifest

tabbify.toml declares how an application builds and runs. It sits at the root of your app directory next to your Dockerfile. The wire struct is vendored identically across tcli, the supervisor, and the node, so the fields mean the same thing everywhere they are read.

There is one public runtime: an OCI image built from your Dockerfile and booted as a CPU-only Firecracker microVM. Runtime configuration sets lifecycle, port, CPU, and memory; it does not select a different execution engine.

tabbify.toml is the unified manifest read by every component. A legacy manifest.toml continues to parse for backward compatibility — see Gotchas for precedence.

A minimal manifest

The smallest manifest declares a name and a build kind:

[app]
name = "hello"

[build]
kind = "docker"   # always "docker" — builds your Dockerfile into an OCI image

Identity: [app]

[app] carries the app's identity. Write name and optionally description. You may pin an ID; otherwise the deploy client derives or mints one for the application.

[app]
id          = "0191e7c2-1111-7222-8333-444455556666"  # your app's permanent address
name        = "booking-site"
description = "Apartami apartment search API"

Use a valid UUID accepted by the current API, or let tcli manage it. The UUID deterministically derives the app's private mesh address and public URL - see Private Mesh and Routing.

Build: [build]

[build].kind is docker: the build produces an OCI image from your Dockerfile.

[build]
kind       = "docker"     # always "docker" — builds an OCI image
context    = "."
dockerfile = "Dockerfile"
builder    = "thinkpad"   # optional: which supervisor compiles the image

builder optionally names the supervisor that compiles the image; leave it unset and Tabbify picks a capable builder for you.

Runtime, ports, and lifecycle: [runtime]

[runtime] carries only lifecycle and resource limits — not a runtime choice. Every app runs as a Firecracker microVM; there is nothing to select.

[runtime]
lifecycle        = "on_request"    # always_on | on_request
idle_timeout_sec = 300
memory_mb        = 512
vcpus            = 1
  • lifecycle — always_on starts at deployment and avoids idle shutdown; 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.

  • port — the guest port your app listens on inside the microVM. A runner binds the app on [app_ula]:8730 over the mesh and the node proxies /app/<uuid> to it; the runner forwards that mesh side to your app's port. It defaults to 8080, so an image that listens on 8080 needs nothing. If your app listens elsewhere, declare it — otherwise the readiness probe hits the wrong port and the app crash-loops:

    [runtime]
    port = 3000
    

    See Routing & public access.

Workload target: [[deploy]]

The current public deployment model selects one workload target. Omit [[deploy]] to let Tabbify choose the default eligible supervisor, or include one block to request a specific supervisor and add target-local environment values:

[[deploy]]
supervisor = "thinkpad"
[deploy.env]
LOG_LEVEL = "debug"

Use at most one [[deploy]] entry for a public deployment. Repeated entries do not create replicas, a highly available group, or a placement policy. They are outside the supported public contract and must not be used to model multiple copies of one application UUID.

Environment and secrets

tabbify.toml does not carry plaintext secrets. A credential lives in the encrypted per-network store (tcli secret set NAME, value via stdin) and the manifest references it by name — the node rewrites each secret:NAME into the real value at deploy time, so the plaintext never touches the manifest, the event log, or the prompt:

[env]
STRIPE_KEY = "secret:STRIPE_KEY"   # resolved from the store at deploy

Non-secret values can be placed in [env]. For credential commands and scoping, see the secret CLI.

Gotchas

  • tabbify.toml takes precedence over a legacy manifest.toml if both exist.
  • Unknown fields may be silently ignored (#[serde(default)], no deny_unknown_fields). That's how old manifests stay compatible — but it also means a typo like lifcycle falls back to the default rather than erroring. Watch your spelling.
  • Runtime-selection keys found in older manifests are inert. New manifests should omit them; every public workload runs in Firecracker.

Once your manifest is ready, deploy it. The simplest path is a git push — put the repo on GitHub with the deploy workflow and Tabbify clones, builds, and runs it for you; see the quickstart. To deploy from your own terminal instead, run tcli deploy --remote, which hands your repo and commit to the node to build and run remotely; see the CLI reference.