Skip to content

Platform architecture

Layer Technology Role
Control plane broker (flect-api) Event-sourced API: scopes, resources, bindings, deploys, resolution
Orchestration Nomad Runs your containers
Routing Traefik HTTPS termination, TLS certificates, reverse proxy
Service discovery Consul Nomad → Traefik wiring
Database sqld SQLite over HTTP, multi-namespace
KV Valkey Redis-compatible store
Object storage Garage S3-compatible object store

The broker is the only thing your app and CLI talk to. It’s an event-sourced control plane: every scope, resource, binding, and deployment is an event in a shared log, and the current state is a projection of that log.

Everything is addressed within a tree:

Org
└── Workspace
└── Project
└── Environment
├── Apps
├── Databases
├── KV stores
└── Object stores

An org is your tenant (you get one on login). You create the workspace / project / environment scopes. A flect.toml is one application in one scope and names it; another environment is another file (flect.dev.toml) with its own resources. A resource belongs to the scope it was created in and is unreachable from any other.

This is the core idea: your app never holds a connection string or a secret.

  1. On deploy, Flect injects exactly two env vars into your container: FLECT_TOKEN (a scoped runtime token) and FLECT_BROKER_URL.
  2. createEnv().db('DB') asks the broker to resolve the binding DB, presenting the token. The token names the service; the broker answers only if that service’s uses lists DB (otherwise 403 BINDING_NOT_USED).
  3. The broker returns a manifest — a temporary, scoped connection with an expiresAt lease (a URL + a sqld namespace, a redis URL + key prefix, or an S3 endpoint + bucket + keys).
  4. The SDK builds the official client from the manifest and caches it until the lease expires, then re-resolves.

No credential is ever baked into your image, printed in your source, or reused across scopes. Rotating access is a matter of the broker issuing a new manifest.

Each resource is isolated at the storage layer, not just by convention:

  • Database — every request carries the resource’s sqld namespace (x-namespace) and a token signed for that namespace only. sqld refuses a request without it (401) and a request for any other namespace (404).
  • KV — every resource has its own Valkey user, allowed only its own key prefix and channels. Commands that reach other keys (SCAN, KEYS, FLUSHALL, …) are refused.
  • Object storage — each store is its own Garage bucket with its own API key pair. There is no cross-bucket access.

flect deploy reads flect.toml, checks it against its scope, binds its resources by id, and submits one Nomad service job per service. Traefik picks the service up from Consul and issues a TLS certificate. An app declares one of three routing modes:

Mode flect.toml Traefik rule
public public = true owns the domain: Host(...)
expose expose = "/path" a path under a shared domain: Host(...) && PathPrefix(/path) (prefix stripped)
private (default) no ingress; reachable only inside the cluster

Public apps get a stable generated hostname:

https://<app-name>-<shortid>.up.flect.run

The shortid is generated once and stays with the app across redeploys. Bring your own domain by setting [app].domain and pointing a CNAME at it.

Limits are enforced org-wide at the control plane; exceeding one returns HTTP 402 with a clear message. Until billing lands there is no plan selection, so every org currently defaults to the Team plan — generous headroom while the platform is pre-billing. Real plan selection and downgrades arrive with billing.

Plan Apps Databases KV Storage
Hobby 3 1 1 1
Pro 10 3 3 3
Team (current default) 25 10 10 10

Deprovisioned resources don’t count toward the limit.