Application Configuration Schema
WBSP applications are configured via a wbsp.yaml at the repo root, passed to
wbsp-app deploy (or the provisioning API). Platform v2 (feature 051)
replaced the v1 targets: model with destination: + components. The v1
targets: model has since been fully removed — the platform is v2-only,
so every wbsp.yaml must declare a destination: block.
Authoritative references (this page is an overview — those are the contract):
- Schema contract:
051-platform-v2-redesign-contracts-cli-commands-md )- v1→v2 migration guide: migration-v1-to-v2.md
- See Example applications for examples in multiple languages.
v1 vs v2
A config must declare a top-level destination: block. The v1 targets:
model has been fully removed — the platform is v2-only. A singular
top-level target: key and the plural targets: block are both rejected with
guidance pointing at the migration guide (they were the
retired v1 deployment keys).
Required fields
| Field | Type | Description |
|---|---|---|
name | string | DNS-safe application name ([a-z0-9][a-z0-9-]*) |
destination | map | One or more named placements, each declaring its own tenant (see below) |
image or dockerfile+source | string | The single component's image, or how to build it |
Identity in v2 is the full taxonomy cluster / tenant / enclave / app / component. The Kubernetes namespace is wbsp-<tenant>-<enclave>-<app>;
managed-resource names embed enough of the taxonomy to stay globally unique
(truncate-middle + hash within DNS/identifier limits).
destination (placements)
A map of named placements; pick one at deploy time with --destination <name>
(optional when exactly one is declared).
| Field | Type | Notes |
|---|---|---|
type | string | required — aws, or one of the local-machine types compose / dev / standalone (feature 063). The old single local type is retired. |
tenant | string | required — DNS-safe tenant identifier for this placement. Declared per-destination (never at the document root); a root-level tenant: is rejected. The same app may use different tenants in different placements. |
mode | string | aws default normal (persistent EKS); also demo, on-demand (Lambda), gvisor, kata (sandbox RuntimeClasses) |
cluster | string | required for aws; ignored for the local-machine types |
enclave | string | required for aws; ignored for the local-machine types. A single-tenant isolation group. Do NOT name it after a retired v1 target/provider (e.g. aws/local) — it collides with the v1 target name in the registry key and breaks --cutover-from. |
env | map | Per-placement env overrides; fold into the global env: with the destination winning |
secrets | map | Per-placement secret-reference overrides (same precedence) |
release | string | Per-placement override of the release command. release: "" disables it for this placement (e.g. a dev destination reusing a shared DB) |
release_timeout | int | Per-placement override of the release timeout (seconds) |
The component
Omitting components: means the top-level image/dockerfile/source/
command/port/access/routes/resources define one unnamed component.
Several workloads → a components: { <name>: {…} } map (each with its own
port/access/routes).
| Field | Type | Default | Description |
|---|---|---|---|
port | int | — | Container port |
access | string | none | public (externally routed via routes), enclave (exposes port to same-enclave apps), none (sibling-only) |
routes | map | — | Required iff access: public on aws. Keyed by destination name → { domain, path_prefix }. Ignored on the local-machine types compose / dev / standalone (served on port). |
replicas | int | 1 | Fixed instance count (mutually exclusive with autoscale) |
autoscale | map | — | { min, max, targetCPU } |
command | list | — | Override the image entrypoint |
resources (managed backing stores)
v2 meaning — this key was repurposed. In v1 it held
replicas/memory/cpu(those moved onto the component). In v2 it declares on-demand managed resources:
| Field | Type | Default | Description |
|---|---|---|---|
database | bool | false | Provision a dedicated PostgreSQL DB + per-app role; injects DATABASE_HOST/_PORT/_NAME/_USER/_PASSWORD (DATABASE_SSL on aws) |
redis | bool | false | Provision a per-app Redis ACL/namespace; injects REDIS_* |
s3 | bool | false | Parsed but NOT yet provisioned (no-op today) |
The platform provisions an empty database. To apply your schema, declare a
release command (the recommended
way) — the platform runs it once per deploy, against the freshly-provisioned DB,
before any traffic reaches the app. (Migrations can still be applied out-of-band
as a break-glass fallback — see the migration guide.) The legacy top-level
database:/redis: blocks are still honoured for back-compat.
release (one-shot migration/setup command)
| Field | Type | Default | Description |
|---|---|---|---|
release | string | — | A command run once per deploy, in the app's own image, with the same injected DATABASE_*/REDIS_*/secret env, after the managed DB is provisioned and before the workload starts or any route is flipped. The canonical use is applying your schema (npx prisma migrate deploy, node lib/migrate.js, …). |
release_timeout | int | 600 | Seconds before the release is killed and the deploy fails. Range 0..3600 (0 ⇒ default). |
Semantics:
- Rollout-gating / fail-closed. A non-zero exit aborts the deploy: no Deployment is created or updated and no routes change. On a redeploy the previous good version keeps serving — a failed migration never ships.
- Runs on every deploy → must be idempotent. Tools like
prisma migrate deployalready skip already-applied migrations; a hand-rolled command should useCREATE … IF NOT EXISTS/ equivalent. - Expand/contract (backward-compatible) only. The release runs the new image's migrations while the old version may still be serving during the rollout, so a migration must be additive and tolerated by the old code. Split a destructive change across two deploys (expand, then contract once no old pods remain).
- The image must carry the tooling. The platform runs an opaque command in
your image — bundle the migration CLI + migration files (e.g. for a Next.js
standalone build, add the
prismaCLI +prisma/to the runner stage; the default trace does not include them). - Per-destination override (see the
destinationtable): setreleaseper placement, orrelease: ""to disable it for a placement (e.g. adevdestination that reuses a shared database). - Where it runs:
aws→ a one-shot Kubernetes Job in the app's namespace under the app's ServiceAccount/IRSA;compose/standalone/dev→docker compose run --rm. Serverless targets (mode: on-demand/demo) cannot run a release and reject arelease:declaration.
resources:
database: true
release: "npx prisma migrate deploy"
destination:
prod: { type: aws, tenant: wbsp, cluster: main, enclave: app1 }
dev: { type: dev, tenant: test, release: "" } # reuse local DB, skip migrateenv, secrets, uses, gateways
env— global key-value env (per-destinationenvoverrides it).secrets— declared by reference (feature 035):APP_VAR: ENV_KEY, resolved from the gitignored.env/.env.<destination>beside the yaml and injected via a protected Secret. A declared secret with no value fails closed.uses— consumer-side peer addressing (same tenant+cluster): the platform resolves(enclave, app, component)and injects host+port env. Forms:<enclave>-<app>-<component>/<app>-<component>/<component>(sibling).gateways— top-level list of authenticated gateways (features 045/046):git/registry(HTTP, Traefik) andssh-git(L4, dedicated NLB front). Only provisioned on aws (local ignores them). Thessh-githost key is stored in a Secret in the app namespace and persists across redeploys; rotate it only via the CLI.
Deploy-time operator config (NOT in wbsp.yaml)
wbsp-app is self-contained and reads these from .env.<destination> beside the
yaml (never the platform's Terraform): WBSP_RDS_HOST (required for
DB-provisioning aws deploys — the Terraform fallback no longer resolves),
WBSP_RDS_PASSWORD, and WBSP_RDS_USER (defaults to wbsp_admin).
Example (v2 single-component web app with a managed DB)
name: my-crm
destination:
dev: { type: dev, tenant: acme } # data services only; app run from the IDE
prod: { type: aws, tenant: acme, mode: normal, cluster: main, enclave: crm }
image: registry.example.com/crm:v2.1.0
port: 8080
access: public
resources:
database: true
routes:
prod: { domain: crm.acme.com } # ignored on the laptop types (compose/dev/standalone — no Traefik)
env:
APP_ENV: productionCLI
wbsp-app deploy --destination <name> [--cutover-from <v1-target>]
wbsp-app stop|maintenance|remove [--destination <name>]--cutover-from <v1-target> migrates a live v1 app in place: the v2 app reuses
the v1 database (no copy/drop) and the v1 deployment is stopped. .env.<target>
becomes .env.<destination>.