Platform architecture
The stack
Section titled “The stack”| 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.
Scopes
Section titled “Scopes”Everything is addressed within a tree:
Org└── Workspace └── Project └── Environment ├── Apps ├── Databases ├── KV stores └── Object storesAn 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.
How resolution works
Section titled “How resolution works”This is the core idea: your app never holds a connection string or a secret.
- On deploy, Flect injects exactly two env vars into your container:
FLECT_TOKEN(a scoped runtime token) andFLECT_BROKER_URL. createEnv().db('DB')asks the broker to resolve the bindingDB, presenting the token. The token names the service; the broker answers only if that service’suseslistsDB(otherwise403 BINDING_NOT_USED).- The broker returns a manifest — a temporary, scoped connection with an
expiresAtlease (a URL + a sqld namespace, a redis URL + key prefix, or an S3 endpoint + bucket + keys). - 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.
Isolation
Section titled “Isolation”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.
Deploy and routing
Section titled “Deploy and routing”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.runThe 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.
Plans and limits
Section titled “Plans and limits”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.