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? }
FieldTypeRequiredDescription
namestringyesDNS-safe application name ([a-z0-9][a-z0-9-]*).
imagestringyes¹Image reference / local build tag.
source + dockerfilestringyes¹Build the image from source (platform builds & pushes to ECR on aws).
destinationmapyesOne or more named placements; each declares its own tenant.
database / redis / s3mapnoOpt 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.
envmapnoNon-secret config (committed).
secretsmapnoSecret env vars declared by reference (values live in .env.<destination>).
releasestringnoOne-shot command run once per deploy, before the workload starts — the canonical way to apply migrations/schema (see Release).
release_timeoutintnoSeconds 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.

FieldTypeApplies toDescription
typestringallcompose, dev, standalone, or aws
tenantstringallOwning tenant for this placement — declared per destination (a root-level tenant: is rejected)
modestringawsnormal (default), demo, on-demand, gvisor, kata
clusterstringawsCluster this placement targets (required for aws)
enclavestringawsApplication-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 .env beside the app with discrete DATABASE_* / REDIS_* values on localhost.
  • 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 (its port is reachable by other apps in the same enclave), or none (internal / sibling-only, the default).
  • routes: a map keyed by destination name, required iff access: public, and ignored on the local-machine types (there a public component is published directly on its port).
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)
FieldTypeDefaultDescription
releasestringCommand run once per deploy, before the workload starts and before any route flip.
release_timeoutint600Seconds 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 deploy already skips already-applied migrations.
  • The image must contain your migration tooling. For a standalone Next.js build, add the prisma CLI + prisma/ to the runner stage (the default trace may omit them).
  • Per-destination override / disable. Under destination.<name>: set release: to a different command, or release: "" to disable it for that placement (e.g. a dev destination reusing a shared database). Unset ⇒ inherit the top-level release:.
  • 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.

Configuration tiers

Split configuration into three tiers (full rules in wbsp-client-guidelines.md §5):

  • Non-secret configenv: (committed); override per destination under destination.<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-injectedDATABASE_* / REDIS_*, provided automatically when database.enabled / redis.enabled are 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 destinations

Environment files: one per destination

Every project following these guidelines provides, for each destination:

  1. a committed .env.<destination>.example template — no secret values, except the shared test-HAP credentials on the local destinations (they are demo credentials, safe to share); and
  2. 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=info

Cloud 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=warn

The 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