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):

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

FieldTypeDescription
namestringDNS-safe application name ([a-z0-9][a-z0-9-]*)
destinationmapOne or more named placements, each declaring its own tenant (see below)
image or dockerfile+sourcestringThe 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).

FieldTypeNotes
typestringrequiredaws, or one of the local-machine types compose / dev / standalone (feature 063). The old single local type is retired.
tenantstringrequired — 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.
modestringaws default normal (persistent EKS); also demo, on-demand (Lambda), gvisor, kata (sandbox RuntimeClasses)
clusterstringrequired for aws; ignored for the local-machine types
enclavestringrequired 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.
envmapPer-placement env overrides; fold into the global env: with the destination winning
secretsmapPer-placement secret-reference overrides (same precedence)
releasestringPer-placement override of the release command. release: "" disables it for this placement (e.g. a dev destination reusing a shared DB)
release_timeoutintPer-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).

FieldTypeDefaultDescription
portintContainer port
accessstringnonepublic (externally routed via routes), enclave (exposes port to same-enclave apps), none (sibling-only)
routesmapRequired iff access: public on aws. Keyed by destination name → { domain, path_prefix }. Ignored on the local-machine types compose / dev / standalone (served on port).
replicasint1Fixed instance count (mutually exclusive with autoscale)
autoscalemap{ min, max, targetCPU }
commandlistOverride 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:

FieldTypeDefaultDescription
databaseboolfalseProvision a dedicated PostgreSQL DB + per-app role; injects DATABASE_HOST/_PORT/_NAME/_USER/_PASSWORD (DATABASE_SSL on aws)
redisboolfalseProvision a per-app Redis ACL/namespace; injects REDIS_*
s3boolfalseParsed 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)

FieldTypeDefaultDescription
releasestringA 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_timeoutint600Seconds 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 deploy already skip already-applied migrations; a hand-rolled command should use CREATE … 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 prisma CLI + prisma/ to the runner stage; the default trace does not include them).
  • Per-destination override (see the destination table): set release per placement, or release: "" to disable it for a placement (e.g. a dev destination 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/devdocker compose run --rm. Serverless targets (mode: on-demand/demo) cannot run a release and reject a release: 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 migrate

env, secrets, uses, gateways

  • env — global key-value env (per-destination env overrides 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) and ssh-git (L4, dedicated NLB front). Only provisioned on aws (local ignores them). The ssh-git host 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: production

CLI

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>.