tabbify.io
Documentation
Product directionEDGE BOX
ReferenceArchitecture

Services

A service in Tabbify is just an app that talks to the outside world — an HTTP API, a chat bot, a job runner that calls a third party. There is nothing special about its packaging: a service is the same thing you ship in the quickstart — a repo with a Dockerfile and a tabbify.toml, deployed by a git push. Tabbify builds it into an OCI image and runs it as a Firecracker microVM. What makes it a service is the configuration around it: the environment it needs, how long it lives, and where it gets built.

Applications in the same tenant can call one another over HTTP at their private workload addresses. Production application traffic currently traverses the managed mesh relay. The platform does not currently provide a shared service catalogue, publishing flow, or access delegation between tenants.

Public access is separate: routing can expose an application at the standard app URL or a configured custom domain with TLS. Making a route public does not create a reusable private service contract.

The manifest

A service uses the same tabbify.toml as any other app. The runtime is always Firecracker — you don't pick one — so the manifest only describes what to build, how long to keep it alive, and where requests go:

[app]
name        = "my-service"
description = "wraps an external API"
# no `id` needed — Tabbify assigns a permanent address and prints it on deploy

[build]
kind = "docker"          # build the Dockerfile into an OCI image

[runtime]
lifecycle        = "always_on"   # start at deploy and avoid idle shutdown
idle_timeout_sec = 300           # (only matters for on_request)
memory_mb        = 512
vcpus            = 1

[routes]
dynamic_prefixes = ["/"]         # send every path to your app

Two lifecycle modes shape how a service runs:

  • on_request — the app sleeps when idle and wakes on the next request. Cheap, but the first request after an idle period waits for the VM to boot. Good for a bot or an endpoint that is hit occasionally.
  • always_on — the app starts on deploy and remains active on the selected host. Use it for background work or to avoid an idle-start delay.

There is no application permission table in this manifest. Isolation comes from the microVM and tenant network; your service must still authenticate and authorize its own application-level requests.

Configuring an external dependency

To wrap a third-party service — calling an API, talking to a database, driving a bot — your app reads its configuration from environment variables, the way any container does. You provide those in tabbify.toml:

[env]
UPSTREAM_URL = "https://api.example.com"
LOG_LEVEL    = "info"

[env] applies to the selected workload. If you choose a supervisor explicitly, one [[deploy]] block can add environment values for that target:

[[deploy]]
supervisor = "ec2-prod"
[deploy.env]
LOG_LEVEL = "debug"

For a secret (an API key, a DB password), don't put the value in tabbify.toml — store it in Tabbify's per-network secret store and reference it by name. The node keeps secrets in an encrypted, per-network store and resolves each secret:NAME reference into the real value at deploy time, so the plaintext never lives in your repo, the manifest, or the event log:

printf '%s' "$DB_PASSWORD" | tcli secret set DB_PASSWORD     # value via stdin
[env]
DB_URL      = "postgres://app@db.internal/app"   # non-secret, plain
DB_PASSWORD = "secret:DB_PASSWORD"               # resolved from the store

At deploy the node rewrites secret:DB_PASSWORD into the decrypted value before the env reaches the microVM; tcli secret list shows names only. A secret: reference with no store configured fails the deploy loudly rather than shipping the app without its credential. See the CLI reference.

Pinning a build host

A service often needs to be built somewhere specific — a machine with the right architecture, or your own self-hosted box. By default the node picks a build host from the fleet; to pin one, name it in [build]:

[build]
kind    = "docker"
builder = "buildbox"   # supervisor display name (or its mesh address)

This only chooses where the image is built. The app still runs as a Firecracker microVM wherever it is deployed.

Service-to-service calls

Within one tenant, configure the provider's private address as an environment variable and call it with a normal HTTP client. Set explicit deadlines, bound retries, and make retried writes idempotent. A private address is reachability, not authentication, so protect sensitive endpoints in the application too.

The relay carries production traffic between workload peers today. Do not use private service calls as a latency or failure-domain assumption without testing the actual deployment. See Private Mesh.

Where to go next