wbsp.yaml Reference
wbsp.yaml is the single declarative file that tells the platform what to
deploy, where to place it, how to route traffic to it, and which
managed resources it needs. You write this file (and a wbsp.Docker); the
platform hides all the Kubernetes, RDS, and routing detail behind it.
This page is the complete field reference for application creators, plus a worked example spanning the five standard destinations and a per-destination environment file for each. For the environment/secret rules this reference builds on, see wbsp-client-guidelines.md §5.
Top level
name: <dns-safe-name> # required — [a-z0-9][a-z0-9-]*
image: <ref> # required (this OR source+dockerfile) — build tag / pull reference
source: <path> # optional — source dir (relative to repo root); triggers an ECR build on aws
dockerfile: <path> # optional — Dockerfile (relative to repo root)
destination: # required — one or more named placements (see Destinations)
<dest-name>: { … }
database: # optional — on-demand managed PostgreSQL
enabled: true|false # default false
redis: # optional — on-demand managed Redis
enabled: true|false # default false
durable: true|false # default false (cache mode); true = persistent store
s3: # optional — on-demand per-app S3 bucket (feature 061)
enabled: true|false # default false (not supported on the compose provider)
env: # optional — non-secret config (committed); override per destination
<KEY>: <value>
secrets: # optional — secret env vars BY REFERENCE (never values here)
<ENV_VAR>: <ENV_KEY_IN_DOTENV>
# Single-component app: declare the one component's fields at the top level —
port: <int>
access: public | enclave | none # default none
routes: { … } # required iff access: public (see Access & routes)
components: # optional — multi-component app (omit for a single component)
<component-name>: { image|dockerfile, command?, port?, access?, replicas?|autoscale?, uses?, routes? }| Field | Type | Required | Description |
|---|---|---|---|
name | string | yes | DNS-safe application name ([a-z0-9][a-z0-9-]*). |
image | string | yes¹ | Image reference / local build tag. |
source + dockerfile | string | yes¹ | Build the image from source (platform builds & pushes to ECR on aws). |
destination | map | yes | One or more named placements; each declares its own tenant. |
database / redis / s3 | map | no | Opt in to a managed PostgreSQL / Redis / per-app S3 bucket (see Resources). The managed PostgreSQL ships pgvector + the standard contrib extensions (aws RDS and the dev/compose pgvector/pgvector container both) — CREATE EXTENSION them from a migration / release: step. |
env | map | no | Non-secret config (committed). |
secrets | map | no | Secret env vars declared by reference (values live in .env.<destination>). |
release | string | no | One-shot command run once per deploy, before the workload starts — the canonical way to apply migrations/schema (see Release). |
release_timeout | int | no | Seconds before the release is killed and the deploy fails (default 600, range 0..3600). |
¹ Provide either image or source+dockerfile.
Destinations
A destination chooses where the app runs. Each key under destination: is a
placement name you choose (dev, stage, prod, …) and becomes the
--destination value at deploy time.
| Field | Type | Applies to | Description |
|---|---|---|---|
type | string | all | compose, dev, standalone, or aws |
tenant | string | all | Owning tenant for this placement — declared per destination (a root-level tenant: is rejected) |
mode | string | aws | normal (default), demo, on-demand, gvisor, kata |
cluster | string | aws | Cluster this placement targets (required for aws) |
enclave | string | aws | Application-isolation group within the cluster (required for aws) |
The three local-machine types run on your own computer and ignore
cluster/enclave/routes:
dev— only the data services run in containers; you run the app from your IDE. The deploy writes a.envbeside the app with discreteDATABASE_*/REDIS_*values onlocalhost.compose— the whole stack (app + per-app PostgreSQL/Redis) runs in containers; production-parity on one machine.standalone— app + database + Redis inside one ephemeral container.
aws runs on the platform's EKS cluster (or, with mode: on-demand, as an
AWS Lambda function). An enclave is an application-isolation group within a
cluster: by default only apps sharing the same tenant + enclave can reach
each other.
Components, access, and routes
A single-component app declares its one component's fields (image/dockerfile,
port, access, routes) at the top level. A multi-component app lists each
workload under a components: map.
access:public(reachable from outside),enclave(itsportis reachable by other apps in the same enclave), ornone(internal / sibling-only, the default).routes: a map keyed by destination name, required iffaccess: public, and ignored on the local-machine types (there apubliccomponent is published directly on itsport).
access: public
port: 8080 # the port your app listens on — the platform's convention is 8080
routes:
stage: { domain: my-app.staging.wbsp-demo.com }
prod: { domain: my-app.acme.com }Resources: PostgreSQL, Redis, and S3
Opt in to managed data stores; the platform provisions them on deploy and
injects connection details as environment variables (DATABASE_* and
REDIS_* — including REDIS_NAMESPACE and REDIS_TLS). Build your client from
these discrete variables; there is no single DATABASE_URL/REDIS_URL, and you
never write connection details into your .env files.
database:
enabled: true
redis:
enabled: true # cache mode (default) — right for caching, sessions, retryable job queues
# durable: true # only when data must survive a node failure (persistent store)
# s3:
# enabled: true # optional per-app S3 bucket (feature 061; not on the compose provider)Each store is a top-level .enabled block — there is no resources: wrapper.
This contract is identical across every destination type — read DATABASE_* /
REDIS_* and your code needs no change between local and cloud.
release (one-shot migration/setup command)
The platform provisions an empty database. To apply your schema, declare a
release: command — the platform runs it once per deploy, in your 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. This is the canonical way to run migrations/seed.
database:
enabled: true
release: "npx prisma migrate deploy" # or "node lib/migrate.js", "npm run db:migrate", …
release_timeout: 600 # optional; seconds, range 0..3600 (default 600)| Field | Type | Default | Description |
|---|---|---|---|
release | string | — | Command run once per deploy, before the workload starts and before any route flip. |
release_timeout | int | 600 | Seconds before the release is killed and the deploy fails (range 0..3600; 0 ⇒ default). |
Semantics:
- Fail-closed and gating. A non-zero exit (or timeout) fails the deploy:
no Deployment is created/updated and no route changes, so on a redeploy the
previous good version keeps serving. A declared
release:is never silently skipped. - Make it idempotent + backward-compatible (expand/contract). The release
runs the new image's migrations while the old version may still be
serving during the rollout, so migrations must be additive and tolerated by the
old code.
prisma migrate deployalready skips already-applied migrations. - The image must contain your migration tooling. For a standalone Next.js
build, add the
prismaCLI +prisma/to the runner stage (the default trace may omit them). - Per-destination override / disable. Under
destination.<name>:setrelease:to a different command, orrelease: ""to disable it for that placement (e.g. adevdestination reusing a shared database). Unset ⇒ inherit the top-levelrelease:. - 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.
Configuration tiers
Split configuration into three tiers (full rules in wbsp-client-guidelines.md §5):
- Non-secret config →
env:(committed); override per destination underdestination.<name>.env:. - Secrets → declared by reference in
secrets:; the values live only in a git-ignored.env.<destination>. A declared secret with no value fails the deploy (fail-closed). - Platform-injected →
DATABASE_*/REDIS_*, provided automatically whendatabase.enabled/redis.enabledare set.
Worked example: wbsp.yaml
One config, five destinations — dev, prod-parity, stage, prod, and
standalone. It declares no secret values: AUTH_CLIENT_SECRET is declared
by reference and supplied per destination in the .env files below.
name: my-app
source: ./
dockerfile: ./wbsp.Docker
destination:
dev: # local — run the app from your IDE
type: dev
tenant: acme
prod-parity: # local — full stack in containers, mirrors prod
type: compose
tenant: acme
standalone: # local — everything in one container
type: standalone
tenant: acme
stage: # aws — staging cluster
type: aws
mode: normal
tenant: acme
cluster: staging
enclave: my-app
prod: # aws — production cluster
type: aws
mode: normal
tenant: acme
cluster: main
enclave: my-app
database:
enabled: true
redis:
enabled: true
env:
LOG_LEVEL: info
secrets:
AUTH_CLIENT_SECRET: AUTH_CLIENT_SECRET # value lives only in .env.<destination>, never here
# Single unnamed component (top level):
port: 8080 # the platform's container-port convention
access: public
routes:
stage: { domain: my-app.staging.wbsp-demo.com }
prod: { domain: my-app.acme.com } # routes are ignored on the three local destinationsEnvironment files: one per destination
Every project following these guidelines provides, for each destination:
- a committed
.env.<destination>.exampletemplate — no secret values, except the shared test-HAP credentials on the local destinations (they are demo credentials, safe to share); and - a git-ignored
.env.<destination>that is as ready-to-run as possible — for the local destinations, fully populated (test HAP included) so the app runs with no manual edits.
Add .env* to .gitignore, and un-ignore the templates with !.env.*.example
(otherwise .env* would also ignore .env.dev.example). DATABASE_* / REDIS_*
are platform-injected — do not put them in these files.
Local destinations — pre-wired to the shared test HAP server
dev, prod-parity, and standalone all point at the shared test HAP server, so
sign-in works locally out of the box. The committed .example templates carry the
same values (they are demo credentials).
# .env.dev (git-ignored; commit .env.dev.example with these same values)
# Auth — shared WBSP test HAP server (demo credentials, safe to share)
AUTH_ISSUER=https://wbsp-auth.wbsp-demo.com
AUTH_CLIENT_ID=hap_lIT7x7SZ30e898mr
AUTH_CLIENT_SECRET=W243TiBRYILUrF0Nzt8stFj-5ZuYGqsT2iCO8gOamJ4
AUTH_REDIRECT_URI=http://localhost:3000/api/auth/callback
LOG_LEVEL=debug# .env.prod-parity (type: compose — full stack locally)
AUTH_ISSUER=https://wbsp-auth.wbsp-demo.com
AUTH_CLIENT_ID=hap_lIT7x7SZ30e898mr
AUTH_CLIENT_SECRET=W243TiBRYILUrF0Nzt8stFj-5ZuYGqsT2iCO8gOamJ4
AUTH_REDIRECT_URI=http://localhost:3000/api/auth/callback
LOG_LEVEL=info# .env.standalone (type: standalone — everything in one container)
AUTH_ISSUER=https://wbsp-auth.wbsp-demo.com
AUTH_CLIENT_ID=hap_lIT7x7SZ30e898mr
AUTH_CLIENT_SECRET=W243TiBRYILUrF0Nzt8stFj-5ZuYGqsT2iCO8gOamJ4
AUTH_REDIRECT_URI=http://localhost:3000/api/auth/callback
LOG_LEVEL=infoCloud destinations — real HAP, placeholders in the committed template
For stage and prod the committed .example uses placeholders; your operator
supplies the real AUTH_CLIENT_SECRET into the git-ignored .env.<destination>.
# .env.stage.example (committed — placeholders only, NO real secret)
AUTH_ISSUER=https://auth.staging.example.com # real staging HAP issuer
AUTH_CLIENT_ID=REPLACE_WITH_STAGING_CLIENT_ID
AUTH_CLIENT_SECRET=REPLACE_WITH_STAGING_CLIENT_SECRET # operator supplies in .env.stage (git-ignored)
AUTH_REDIRECT_URI=https://my-app.staging.wbsp-demo.com/api/auth/callback
LOG_LEVEL=info# .env.prod.example (committed — placeholders only, NO real secret)
AUTH_ISSUER=https://auth.example.com # real production HAP issuer
AUTH_CLIENT_ID=REPLACE_WITH_PROD_CLIENT_ID
AUTH_CLIENT_SECRET=REPLACE_WITH_PROD_CLIENT_SECRET # operator supplies in .env.prod (git-ignored)
AUTH_REDIRECT_URI=https://my-app.acme.com/api/auth/callback
LOG_LEVEL=warnThe redirect path is /api/auth/callback everywhere; only the host differs by
destination. Register each destination's callback URL with HAP (see the
Authentication guidance) — HAP matches
redirect_uri by exact string.
See also
- wbsp-client-guidelines.md — project conventions, secrets, auth, logging.
- application-creator-guide.md — the full deploy walkthrough and CLI reference.
- useful-commands.md — commands for
compose/dev/standaloneandaws.