WBSP Application Creator Guide
Audience: Application creators who want to deploy and manage applications on the WBSP platform. You do not need Kubernetes, infrastructure, or cloud provider experience to use this guide.
Not what you're looking for? Setting up or administering the platform itself is a separate, operator-only concern and is out of scope for this guide.
Table of Contents
- Using the
/wbsp-buildskill - Architecting for the Platform
- Preparing Your Application
- Configuration
- Developing Outside the Platform
- Deploying Your Application
- Monitoring Your Application
- CLI Reference
- REST API Reference
- Logs and Debugging
- Contacting the Platform Team
Using the /wbsp-build skill
If you downloaded wbsp and run wbsp init . and then run the `/wbsp-build'
skill to build your application, it will (in most cases) follow these
guidelines automatically.
Expect to be asked a few questions early on. At its Set project direction step the pipeline researches your project, forms an opinion, and then asks you to decide:
- Single-tenant or multi-tenant? (the platform prefers single-tenant)
- A single app under
/target, or a monorepo with source trees at the repository root? - Parallel services — does your application need non-application services running alongside it (a git server, a search engine, analytics, …), beyond the managed database, Redis and S3 the platform provides every app?
- Topology — how the app ships as Docker images:
single(one program, one image),combined(several programs sharing one image), orsplit(separately built images — cloud-only, as a direct deployment arranged with the platform operator). - Which web framework — asked only if your project has a web interface and the research turns up a concrete reason not to use Next.js, which is the platform default. If there is no such reason you will not be asked.
The pipeline will not answer these for you: in a session where it cannot reach
you (headless or CI) it defers the step rather than guessing. Your answers, plus
what the pipeline works out about your project's components, are recorded as the
project's decisions in DECISIONS.md and in a Decisions: block in
wbsp-status.md, and /wbsp-status shows them back to you at any time.
Architecting for the Platform
The WBSP platform deploys your application as a Docker container. To ensure a smooth deployment, follow these guidelines:
Design for Statelessness
Your application should not store persistent data on the local filesystem. The platform may restart or replace your container at any time. Use the platform-provided database for persistent storage and environment variables for configuration.
Listen on Port 8080
Your application must listen for HTTP traffic on port 8080 — the platform's container-port convention. The platform's ingress layer routes external traffic to this port automatically, and you do not need to configure TLS — the platform handles that.
Keep the port consistent across your app: listen on 8080, EXPOSE 8080 in your
Dockerfile, and declare port: 8080 in your config (the port: field defaults to
8080). Use 8080 in your examples rather than a framework default like 3000,
so the same image runs identically on every destination.
Accept Configuration via Environment Variables
The platform injects configuration into your container as environment variables. Your application should read database credentials, feature flags, and runtime settings from the environment rather than config files baked into the image.
When you declare a database dependency, the platform automatically injects these variables:
| Variable | Description |
|---|---|
DATABASE_HOST | Hostname of the provisioned Postgres instance |
DATABASE_PORT | Port number (typically 5432) |
DATABASE_NAME | Name of your application's database |
DATABASE_USER | Username for database access |
DATABASE_PASSWORD | Password for database access |
DATABASE_SSL | Set to true when the database requires SSL (e.g., AWS RDS) |
Your application should handle DATABASE_SSL — when set to true, enable SSL/TLS in your database client. On local, this variable is not set.
You can also define custom environment variables in your configuration file (see Configuration).
Declare Dependencies Explicitly
If your application needs a database, declare it in your configuration file. The platform provisions resources on demand — only what you request is created. If you don't declare a database, none is provisioned.
Preparing Your Application
Installing the CLI
Install the wbsp CLI (no source-repo access required):
curl -fsSL https://get.wbsp.ai/install.sh | bashYou'll also need Docker and Docker Compose v2. Full options (pinning a version, choosing an install dir, upgrading) are in Installing the WBSP CLIs.
Writing a Dockerfile
Your application must be packaged as a Docker image. Here is a minimal example:
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --production
COPY . .
EXPOSE 8080
CMD ["node", "server.js"]Key points:
- Expose port 8080 — the platform routes traffic to this port
- Keep images small — use Alpine-based images where possible
- Don't include secrets — credentials are injected via environment variables at runtime
Building Your Image
docker build -t my-app:v1.0.0 .Making Your Image Available
Local: Images built on the same machine are available automatically.
AWS with source and dockerfile: If your configuration file includes source and dockerfile fields, wbsp deploy builds the image locally and pushes it through the platform registry gateway to ECR automatically. You don't need to build or push anything manually, and you don't need AWS credentials:
variant: 7a1d8030-1234-4abc-8def-0123456789ab # from `wbsp whoami`
name: my-crm
image: my-crm:latest
source: path/to/source
dockerfile: path/to/Dockerfile
destination:
dev:
type: dev
tenant: acme-corp
prod:
type: aws
tenant: acme-corp
cluster: main
enclave: crm
access: public
routes:
prod: { domain: crm.acme.com } # ignored on the `dev` (local-machine) destinationThe image is built with --platform linux/amd64 (required for EKS nodes) and pushed to the variant's container repository — wbsp-v-<short-uuid>-<image>, derived from your variant: UUID, the same repository git push uses. The image field is only the local build tag; the pushed reference is resolved automatically.
You do not run aws ecr … docker login. The push goes through the platform's registry gateway (registry.wbsp.ai/<owner>/<repo>/app) off your wbsp login session, and the gateway injects the ECR credential server-side — no AWS keys ever reach your machine. See credential-less-deploy.md.
Direct ECR login is the operator
--backdoorpath only (it needsAWS_PROFILE), for bootstrap and recovery — a normal application creator never uses it:# operators only, with --backdoor + AWS_PROFILE aws ecr get-login-password | docker login --username AWS --password-stdin <account>.dkr.ecr.<region>.amazonaws.com docker tag my-app:v1.0.0 <account>.dkr.ecr.<region>.amazonaws.com/wbsp-v-<short-uuid>-app:v1.0.0 docker push <account>.dkr.ecr.<region>.amazonaws.com/wbsp-v-<short-uuid>-app:v1.0.0
Testing Locally Before Deploying
Before deploying to the platform, verify your image runs correctly:
docker run -p 8080:8080 -e APP_ENV=development my-app:v1.0.0Visit http://localhost:8080 to confirm your application responds.
Configuration
Applications are configured via a wbsp.yaml file in the repository root —
never under /target (a target/wbsp.yaml is wrong and the platform tooling
will not find it; with code under /target, use source: target). This file
tells the platform what to deploy, where to route traffic, and what resources
to provision.
wbsp-yaml-reference.md is the single source of truth for the schema — every field, destination type, mode, and validation rule. This section is a working introduction to the fields you'll use most.
Required Fields
| Field | Type | Description |
|---|---|---|
variant | string (UUID) | Your catalog variant UUID, from wbsp whoami (convention: place it immediately before name). Image-producing deploys are refused without it. See Recording your variant. |
name | string | DNS-safe application name ([a-z0-9][a-z0-9-]*) |
image or dockerfile+source | string | Docker image reference (e.g., myregistry/my-app:v1.0.0), or how to build it |
destination | map | One or more named placements, each declaring its own tenant (see below) |
The
tenantis declared per-destination (under each entry ofdestination:), never at the document root — a root-leveltenant:is rejected.
Recording your variant
wbsp whoami resolves your repository's git remote to its catalog variant. Run
it in your repository and paste the UUID into wbsp.yaml, or patch it in
directly — the bare form prints only the UUID, so it is safe to substitute:
wbsp whoami
# 64656751-e7f1-43d7-b3aa-f9723170e4d5
yq -i ".variant = \"$(wbsp whoami)\"" wbsp.yamlThis is a one-time step per repository: every other command reads the UUID from
wbsp.yaml rather than contacting the catalog.
If wbsp whoami fails or surprises you, run wbsp whoami --all. It shows
the signed-in account next to the repository's owner, and warns when they
disagree:
warning: signed in as philcal@mac.com but the repository is owned by philip-callender-hh70That mismatch is the usual cause of a 403 on git push or wbsp deploy —
you are signed in as one account, but a different account owns the variant. Fix
it by signing in as the owning account (wbsp logout then wbsp login), or ask
an operator to add you as a collaborator.
--all also warns when the variant: already in your wbsp.yaml no longer
matches what your remote resolves to — worth checking after a repository has
been renamed, forked, or re-pointed.
See useful-commands.md for every field whoami reports
and the --json contract for automation.
Build Fields (Optional)
When provided, the platform builds and pushes the image automatically on AWS deploys.
| Field | Type | Description |
|---|---|---|
source | string | Path to application source directory (relative to repo root) |
dockerfile | string | Path to Dockerfile (relative to repo root) |
Destinations, components, enclaves
A config splits where an app runs from what runs:
- destination — where the app runs (placement only). A destination has a
type(compose,dev,standalone, oraws) and, foraws, amode; anawsdestination also names theclusterandenclaveit lands in. The local-machine types (compose/dev/standalone) need neither. - component — what runs. A single-component app declares
image/dockerfile/port/access/routesat the top level (this is the one unnamed component); a multi-component app lists each workload under acomponents:map.
Destinations
Each key under destination: is a placement name you choose (dev, demo,
prod, …) — it becomes the --destination value at deploy time.
| Field | Type | Applies to | Description |
|---|---|---|---|
type | string | all | compose, dev, standalone, or aws |
mode | string | aws | normal (aws default), on-demand, gvisor, kata — demo is reserved to the demo/sandbox destinations and may not be declared |
cluster | string | aws | Cluster this placement targets (required for aws) |
enclave | string | aws | Application-isolation group within the cluster (required for aws) |
The aws modes are: type: aws (with mode: normal, the default) for an
always-on EKS deployment, and type: aws, mode: on-demand for an AWS Lambda
deployment. A throwaway sample-data instance is not a mode you declare — it is
the reserved demo destination (demo: {}; the platform supplies its whole
identity). Two more reserved names exist: sandbox (a work-in-progress demo)
and universal (publish an appliance-runnable image, deploy nothing) — see
Reserved destinations.
An enclave is an application-isolation group within a single cluster.
NetworkPolicies select peers by tenant + enclave, so by default only apps
sharing both can talk to each other; the Kubernetes namespace becomes
wbsp-<tenant>-<enclave>-<app>. The local-machine types
(compose/dev/standalone) relax these controls and ignore
cluster/enclave.
Components, access, and routes
A component declares 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 in v2 is a map keyed by destination name (not a list), declared on
the component. It is required iff access: public, and is ignored on the
local-machine types (compose/dev/standalone) — there a public
component is published directly on its port.
access: public
routes:
demo: { domain: hello.wbsp-demo.com } # key = destination name; ignored on a local-machine destinationWorked example (single component)
variant: 7a1d8030-1234-4abc-8def-0123456789ab # from `wbsp whoami`
name: hello
destination:
dev: # placement: local-machine (run app from your IDE)
type: dev
tenant: test
staging: # placement: aws cluster "staging", enclave "app1"
type: aws
mode: normal
tenant: test
cluster: staging
enclave: app1
# Single unnamed component declared at the top level:
dockerfile: ./Dockerfile
port: 8080 # the platform's container-port convention (see "Listen on Port 8080")
access: public
routes:
staging: { domain: hello.example.com } # ignored on the `dev` (local-machine) destinationNote the second destination is named staging, not demo. demo is a
reserved name: the platform supplies its whole identity, so it is declared as
demo: {} with no type, mode, tenant, cluster or enclave — declaring
any of those under it is refused. Pick your own name for an ordinary AWS
placement.
Deploy it with wbsp deploy hello --destination staging (the
--destination flag is optional when the config declares exactly one
destination). See wbsp-yaml-reference.md for a worked
config spanning dev, prod-parity, stage, prod, and standalone.
Multi-component apps and inter-app deps
A multi-component app moves each workload under components: { <name>: {…} },
and references peer components/apps with uses: using
<enclave>-<app>-<component> addressing (the platform injects the peer's
host+port as environment variables).
Incremental today. Multi-component (
components:) deploy parses and validates but is not yet fully wired — only the single unnamed component is folded into the deploy path so far. Treat it as forward-looking. Managed data stores use the top-leveldatabase.enabled/redis.enabled/s3.enabledblocks (theresources: { database, redis, s3 }shape has been removed).
Route Entry
| Field | Type | Default | Description |
|---|---|---|---|
domain | string | required | Fully qualified domain name |
path_prefix | string | / | URL path prefix for sub-routing |
strip_prefix | boolean | true | Strip the path prefix before forwarding to your app (see Path Prefixes) |
Optional Fields
database
| Field | Type | Default | Description |
|---|---|---|---|
enabled | boolean | false | Provision a dedicated PostgreSQL database |
shared_with | string | — | Share another app's database instead of provisioning a new one |
extensions | list of string | — | PostgreSQL extensions the platform installs before your app or its sample data touches the database |
Extensions. The
vector(pgvector) extension and the standard contrib bundle (pg_trgm,hstore,uuid-ossp,pg_stat_statements, …) are available onaws(RDS), on thedev/composecontainers, and in the bundled engine used bystandalone,demoand sandbox.postgisis also available: declare it and the bundled engine used bystandalone,demoand sandbox switches to a spatial variant that carries it, and RDS enables it like any other extension. Declare every extension you need — the platform enables exactly what you ask for, and an extension you did not declare is not there. See thewbsp.yamlreference, which is the source of truth for that file.If your schema uses one, declare it — do not rely on installing it yourself:
database: enabled: true extensions: [vector]Most PostgreSQL extensions are untrusted, which means
CREATE EXTENSIONrequires superuser — and your application's database role is not one, on any destination. Ondemo/sandbox/standalonethis bites hardest, because your sample data is loaded as that role:pg_dumpemitsCREATE EXTENSION IF NOT EXISTS vectorwhenever your schema uses the type, the statement is rejected, the seed aborts, and the instance never becomes ready. Your migrations cannot rescue it either — they run after the seed, as the same role. Declaring the extension is the only ordering that works: the platform creates it with the privilege it has, before anything else runs.Keep your own
CREATE EXTENSION IF NOT EXISTS <name>in the migration if you have one — once the extension exists it is a no-op that needs no privilege.
Scaling
| Field | Type | Default | Description |
|---|---|---|---|
resources.replicas | integer | 1 | Fixed instance count for the single unnamed component |
components.<n>.replicas | integer | 1 | Fixed instance count for a named component (mutually exclusive with autoscale) |
components.<n>.autoscale | map | — | { min, max, targetCPU } (named components only) |
redis
| Field | Type | Default | Description |
|---|---|---|---|
enabled | boolean | false | Provision a managed Redis (injects REDIS_*, including REDIS_NAMESPACE and REDIS_TLS) |
durable | boolean | false | false = cache mode (ElastiCache; treat as ephemeral). true = persistent store (MemoryDB) for data that must survive a restart |
s3
| Field | Type | Default | Description |
|---|---|---|---|
enabled | boolean | false | Provision a per-app S3 bucket + IRSA role (feature 061). Not supported on the compose provider. |
Enable managed stores with the top-level
database.enabled,redis.enabled, ands3.enabledblocks. (The oldresources: { database, redis, s3 }block has been removed — use the.enabledblocks.)
env
A key-value map of environment variables injected into the container.
Example: Simple Application
variant: 7a1d8030-1234-4abc-8def-0123456789ab # from `wbsp whoami`
name: my-crm
image: myregistry/crm:v2.1.0
destination:
dev:
type: dev
tenant: acme-corp
prod:
type: aws
tenant: acme-corp
cluster: main
enclave: crm
database:
enabled: true
access: public
routes:
prod: { domain: crm.acme.com } # ignored on the `dev` (local-machine) destination
env:
APP_ENV: production
LOG_LEVEL: infoExample: Multi-Route Application
variant: 7a1d8030-1234-4abc-8def-0123456789ab # from `wbsp whoami`
name: portal
image: myregistry/portal:latest
destination:
dev:
type: dev
tenant: acme-corp
prod:
type: aws
tenant: acme-corp
cluster: main
enclave: portal
access: public
routes:
# one route entry per destination; mount under a path prefix with `path_prefix`
prod: { domain: apps.acme.com, path_prefix: /portal }Example: Application with Automatic Build
variant: 7a1d8030-1234-4abc-8def-0123456789ab # from `wbsp whoami`
name: my-crm
image: my-crm:latest
source: src/crm
dockerfile: src/crm/Dockerfile
destination:
dev:
type: dev
tenant: acme-corp
prod:
type: aws
tenant: acme-corp
cluster: main
enclave: crm
database:
enabled: true
access: public
routes:
prod: { domain: crm.acme.com }
env:
APP_TITLE: My CRMOn an aws destination, the platform builds the image from source using the specified dockerfile, pushes it to ECR, and deploys it. On a local-machine destination (compose/dev/standalone), the source/dockerfile fields are ignored and the platform uses the image field directly (you must build it yourself with docker build).
Example: Shared Database
Multiple apps can share a single database. Deploy the primary app first, then reference it with shared_with:
# primary app — owns the database
variant: 7a1d8030-1234-4abc-8def-0123456789ab # from `wbsp whoami`
name: app-one
image: app-one:latest
destination:
dev:
type: dev
tenant: acme-corp
prod:
type: aws
tenant: acme-corp
cluster: main
enclave: shared
database:
enabled: true
access: public
routes:
prod: { domain: app-one.acme.com }
env:
APP_TITLE: App One# secondary app — shares app-one's database
variant: 0e25c1b4-5678-4def-9abc-fedcba987654 # each app has its OWN variant UUID
name: app-two
image: app-two:latest
destination:
dev:
type: dev
tenant: acme-corp
prod:
type: aws
tenant: acme-corp
cluster: main
enclave: shared
database:
shared_with: app-one
access: public
routes:
prod: { domain: app-two.acme.com }
env:
APP_TITLE: App TwoThe secondary app receives the same DATABASE_* environment variables as the primary. The primary app must be deployed first — the platform reads its credentials from state. Both apps connect to the same database, so design your schema accordingly.
Example: Application with Resource Limits
variant: 7a1d8030-1234-4abc-8def-0123456789ab # from `wbsp whoami`
name: api-service
image: myregistry/api:v3.0.0
destination:
dev:
type: dev
tenant: acme-corp
prod:
type: aws
tenant: acme-corp
cluster: main
enclave: api
database:
enabled: true
resources:
replicas: 2 # scale the single unnamed component
# Component fields (single unnamed component) live at the top level:
access: public
routes:
prod: { domain: api.acme.com }
env:
APP_ENV: productionConnecting to Another Application's Service (Inter-App Access)
By default applications are network-isolated. To let other applications call your app's service, declare the ports you expose and who may reach each one. Access is controlled entirely by you (the provider) — another application can never grant itself access to your service.
Expose a port (provider)
ports:
- name: auth # how consumers refer to it
port: 8080 # the TCP port your app listens on
access: tenant # tenant (default) | any | public
allow: # optional cross-tenant allow-list
- wbsp/ai-native-crm # a specific app: tenant/app
- wbsp/* # all apps in a tenant: tenant/*- A declared port is always reachable by other apps in your own tenant.
access: anyopens it to any tenant;access: publicto the Internet.allow:grants specific cross-tenant apps without opening to everyone.
Consume a port (consumer)
uses:
- app: test/headless-auth # provider tenant/app
port: auth # provider port name or number
alias: AUTH # optional env-var prefixOn deploy the platform injects connection details (no hard-coded endpoints):
HEADLESS_AUTH_AUTH_HOST=test-headless-auth.wbsp-test-headless-auth.svc.cluster.local
HEADLESS_AUTH_AUTH_PORT=8080(With alias: AUTH the variables are AUTH_HOST / AUTH_PORT.)
Rules to remember
- You can't self-grant. Adding a
uses:entry for a service you aren't authorized for gets you nothing — the provider must expose the port to your tenant (same-tenant), allow-list your app, or set it toany. - Same-tenant is open: apps in one tenant can reach each other's declared
ports automatically; a
uses:entry just adds the injected connection env. - The platform controls reachability only. Authenticating the caller (if your service needs it) is your application's responsibility — use your own API keys/tokens over the connection.
- No secrets go in
ports:/uses:or the injected connection details. - Enforcement is real on AWS (EKS network policy); the local-machine types
(
compose/dev/standalone) do not enforce it (open network) but inject the same connection env.
Developing Outside the Platform
During active development you want fast feedback — hot reload, instant code changes, AI-assisted editing — without waiting for a Docker build and deploy cycle on every change. A dev destination lets you run your application from your IDE while the platform runs everything else in containers: your PostgreSQL and Redis.
How It Works
A dev-type placement runs only the data services (PostgreSQL and Redis) in containers; the app itself is run from your IDE with live reload. When you deploy a dev destination, the platform:
- Provisions your database and Redis (if declared in config) in containers
- Host-publishes their ports, derived from your project number (Postgres
54NNN, Redis63NNN) - Writes a
.envnext to your app with discreteDATABASE_*/REDIS_*values pointing atlocalhoston those derived ports
There is no app container and no Traefik for a dev destination — routes are ignored. You run the app yourself and it connects to the containerised data services.
Starting the Dev Environment
Give the placement type: dev in your config, then deploy it. This brings up the DB/Redis containers and writes the .env:
wbsp deploy --config my-app.yaml --destination devThen source the generated .env and run your app from your IDE:
source .env
# Next.js
npm run dev
# Go
go run .
# Python / Django
python manage.py runserver
# Any framework — it reads DATABASE_* / REDIS_* from the environmentMost frameworks (Next.js, Django, Rails, Spring Boot) load the .env automatically, so you may not even need source.
Dev Alongside Other Apps
A dev destination applies to a single app. Other apps in your tenant continue running in their own placements. This lets you develop one app while interacting with the rest of the environment:
# Bring up supporting apps in containers (whole stack)
wbsp deploy --config api-service.yaml --destination compose
wbsp deploy --config worker.yaml --destination compose
# Run just the frontend from your IDE
wbsp deploy --config frontend.yaml --destination devStopping and Tearing Down
Stop (preserve the database and Redis data):
wbsp stop my-crm --tenant acme-corpRemove (tear down and delete the data):
wbsp remove my-crm --tenant acme-corp --forceTo switch to the fully containerised version of the app, deploy a compose (or standalone) destination instead — wbsp deploy --config my-app.yaml --destination compose.
Dev Environment Variables
A dev destination exposes the same discrete DATABASE_* / REDIS_* env contract as aws (there is no DATABASE_URL / REDIS_URL). The values point at localhost on host-published, project-derived ports so your IDE-run app can connect:
| Variable | dev Value | aws Value |
|---|---|---|
DATABASE_HOST | localhost | RDS hostname |
DATABASE_PORT | Derived host port (54NNN) | 5432 |
DATABASE_NAME | Same | Same |
DATABASE_USER | Same | Same |
DATABASE_PASSWORD | Same | Same |
REDIS_HOST | localhost | ElastiCache hostname |
REDIS_PORT | Derived host port (63NNN) | 6379 |
(Ports are derived from your project number; values above 65535 fold into [49152,65535].) Custom environment variables from your config file's env section are included as well.
Requirements
- Docker Desktop (macOS or Windows) to run the data-service containers
- No local platform to provision — the
devtype is self-contained (the data-service containers are created by the deploy itself) - Run your app from your IDE; it reads the host/port values from the generated
.env
Deploying Your Application
Prerequisites
- The
wbspCLI is installed (see Installing the CLI). The CLI is self-contained — there is no local platform daemon to start. - Docker is running (for the local-machine destinations
compose/dev/standalone, which run your containers on your own machine). - You have a Docker image available (see Preparing Your Application).
- You have a YAML configuration file (see Configuration).
- For an
awsdestination only: your platform operator has already provisioned the target cluster (you do not run or manage the platform yourself).
Deploy via CLI
wbsp deploy --config my-app.yaml --destination devAdd --diagram <file> to also write a deployment-diagram prompt for your app — a
text file containing an image-generation prompt (paste into Google "Nano Banana")
plus a mermaid block you can render in VS Code, GitHub, or mermaid.live. It
shows your app, its pods, port, routing, and the Postgres/Redis it uses, and
never contains secrets:
wbsp deploy --config my-app.yaml --destination dev --diagram my-app.diagram.txt(Platform operators can render the whole-landscape view with
wbsp-platform diagram landscape.txt --provider <t>.)
On success, the CLI outputs a JSON result with your application's status, routes, and database details (if provisioned):
{
"application_id": "acme-corp-my-crm",
"name": "my-crm",
"tenant": "acme-corp",
"status": "running",
"routes": ["http://crm.acme.com/"],
"database": {
"provisioned": true,
"host": "postgres",
"port": 5432,
"database": "acme_corp_my_crm"
}
}The
wbspCLI is the deployment interface for application creators. The platform also exposes an HTTP API for programmatic use (see REST API Reference), but it is not a local server you start — it lives at the platform endpoint your operator provides.
Verifying Your Deployment
After deploying, check that your application is running:
wbsp status my-crm --tenant acme-corpDeployment Types
The three local-machine types (compose, dev, standalone) all run on the
developer's machine, expose the same discrete DATABASE_* / REDIS_* env
contract as aws, use no Traefik (routes are ignored), and host-publish ports
derived from your project number. Each is selected by a destination's type:
(and, for aws, its mode:) and deployed with --destination <name>.
| Aspect | compose | dev | standalone | AWS (always-on) | AWS (on-demand) | AWS Sample |
|---|---|---|---|---|---|---|
Destination type / mode | type: compose | type: dev | type: standalone | type: aws (mode: normal) | type: aws, mode: on-demand | reserved name demo — declare demo: {}, no type/mode |
| What runs | App + DB + Redis containers | DB + Redis containers; app from your IDE | App + DB + Redis in one container | ECR (auto-built) | ECR + Lambda Web Adapter | Ephemeral EKS pod: app + bundled Postgres+Redis (gVisor) |
| Image source | Local Docker images | Local Docker images (data services only) | Local Docker images | ECR (auto-built) | ECR + Lambda Web Adapter | ECR (prepared demo image) |
| Database | Per-app local Postgres | Per-app local Postgres | Embedded in the container | Amazon RDS | Shared RDS | Bundled in-pod Postgres (ephemeral) |
| Scaling | Fixed replicas | n/a (run from IDE) | Single container | Fixed replicas | Scale-to-zero | Scale-to-zero (per-launch pod) |
| Cold start | None | None | None | None | ~10s (VPC) | ~30-60s (pod boot + seed) |
| Cleanup | Manual | Manual | Manual (ephemeral) | Manual | Manual | Auto (configurable timeout) |
| Use case | Production-parity local | Inner-loop development | Throwaway local instance | Production | Low-traffic apps | Demos and trials |
A seventh destination, the reserved universal name, is deliberately absent
from this table: it deploys nothing. It publishes a multi-architecture image
so your app can run on a home appliance or an Apple Silicon machine — see
Universal Destination.
On-Demand Destination (type: aws, mode: on-demand)
Add an aws destination with mode: on-demand to deploy as an AWS Lambda function using the Lambda Web Adapter — your code runs without modification. The function connects to the same RDS database as always-on mode and scales to zero when idle.
destination:
lambda:
type: aws
mode: on-demand
tenant: acme-corp
cluster: main
enclave: my-app
access: public
routes:
lambda: { domain: my-app-lambda.example.com }AWS Sample / Demo Destination (the reserved demo name)
A demo runs your app unchanged as an ephemeral, per-user cloud instance with
real, bundled datastores — a wire-compatible PostgreSQL (and Redis, if your
app uses it) packaged inside the demo image, seeded from your standard dump. No
shared RDS/ElastiCache is created, and nothing runs when idle (scale-to-zero).
There is no SQLite substitute — your app connects to the bundled Postgres via
the normal DATABASE_* variables, exactly as in production.
It works in three parts:
-
Publish (one-time per release) —
wbsp deploy --destination demobuilds and pushes the demo image to ECR. Nothing runs yet:wbsp deploy --config demo-app.yaml --destination demo # Builds the app onto the bundled Postgres+Redis stack, bakes in the sample # data, pushes to ECR. Prints the published image ref — no instance started. -
Launch (on demand) — the wbsp.ai website's "Try the demo" button (or the CLI parity
wbsp demo launch) starts a personal instance at a unique, unguessable subdomain and returns its URL +expires_at:wbsp demo launch # in the app dir — variant + tenant come from wbsp.yaml # Demo URL: https://my-crm-a3f8b2c1.wbsp-demo.com/ (expires in 10 minutes)The instance token (
a3f8b2c1) becomes a subdomain, so the app always runs at/with no path-prefix complexity. The base domain is configured by the platform operator. A visitor already signed into wbsp.ai is signed into the demo automatically (shared-HAP SSO — no second login). -
Teardown — the platform reaps the instance after the timeout (default 10 min); for a short grace window the URL shows a "this demo has ended" page rather than an error, then stops resolving. All demo data is discarded.
Your app can detect demo mode via the WBSP_SAMPLE_MODE environment variable
(used to run background jobs inline — see the client contract).
Authentication in a Demo (no per-app OIDC client)
A demo deploy needs no auth client secrets. Every demo/sandbox pod receives the shared demo OIDC client, injected by the platform at launch:
| Variable | Meaning |
|---|---|
AUTH_HAP_ISSUER | Demo-tenant HAP issuer — discovery at ${AUTH_HAP_ISSUER}/.well-known/openid-configuration |
AUTH_HAP_ID / AUTH_HAP_SECRET | The shared client, registered once with a *.wbsp-demo.com wildcard redirect |
NEXT_PUBLIC_HAP_ENABLED | true — enables hosted-auth mode in the app |
The injection happens after your app's own env, deliberately overriding any client you baked in — that is what signs a wbsp.ai visitor into your demo via silent SSO. Your app should read these variables for demo auth (map them onto your auth library's setting names at startup if it expects different ones).
Consequences:
- Do not declare OIDC/auth client variables (client id/secret,
issuer/discovery URL) under
secrets:for the demo destination, and do not put them in.env.demo— the deploy would fail-closed waiting for values the platform injects anyway. - Do not put placeholder values in
.env.demoto get past the check: the launch overlay only overrides keys with the same names, so an app reading its ownOIDC_*names would boot with the placeholders and login would break at runtime. - Do not register a separate HAP client per demo app — the shared client is the design; there is no per-app registration step.
sample_data Configuration
For the reserved demo (or sandbox) destination, add a sample_data section.
The seed is a standard PostgreSQL dump (generate it with wbsp dump-data), loaded as-is into the demo's bundled Postgres:
| Field | Type | Default | Description |
|---|---|---|---|
sample_data.path | string | required | Directory of standard .sql seed files baked into the demo image |
sample_data.timeout_minutes | int | 10 | Auto-cleanup timeout (instance TTL) |
sample_data:
path: examples/sample-data/crm
timeout_minutes: 10For local development, add your application's domain to /etc/hosts:
127.0.0.1 crm.acme.comUniversal Destination (the reserved universal name)
Every destination above deploys your app somewhere. universal is the one
that does not: it builds your image for both the cloud architecture
(linux/amd64) and Apple Silicon (linux/arm64), publishes the two as a single
multi-architecture index, and stops. Nothing is deployed, nothing runs, no
database or route is provisioned, and no tenant is involved at any point.
Use it when you want your application to run on a home appliance (wbsp-vm) or
any Apple Silicon machine — including before you enable "can run locally" in the
catalog. A cloud deploy publishes linux/amd64 only, so an appliance pulls that
image cleanly and then fails with exec format error.
Like demo and sandbox, universal is a reserved name: the platform
supplies its whole identity, so you declare no type, tenant, cluster or
enclave (declaring any of them is a hard error).
name: my-app
dockerfile: Dockerfile
release: "npx prisma migrate deploy"
destination:
universal: {}wbsp deploy --config path/to/wbsp.yaml --destination universal{
"app": "my-app",
"destination": "universal",
"image_ref": "registry.wbsp.ai/<owner>/<variant>/app:latest",
"platforms": ["linux/amd64", "linux/arm64"],
"status": "published",
"deployed": false
}Four things worth knowing:
- It is the whole flow for an appliance-only app — one with no cloud destination at all. Publish, then enable "can run locally" in the catalog.
- Order matters when you also deploy to the cloud. A cloud deploy moves the
image tag to a cloud-only manifest, so run
--destination universallast. A cloud deploy of an app that declaresuniversal:prints a reminder. release:travels on the image. An appliance cannot read yourwbsp.yaml; it reads the run manifest stamped on the image, built from this destination's effective configuration. An emptyuniversal: {}inherits your top-levelrelease:— override it under the block when the appliance needs something different. Unlike the serverless placements,universalaccepts arelease:; the appliance is precisely what runs it.- Login only. Publishing goes through the registry gateway with your
wbsp loginsession and needs no AWS credentials.--backdooris refused.
Full field rules are in wbsp-yaml-reference.md; the commands and troubleshooting table are in useful-commands.md.
Monitoring Your Application
Check Application Status
CLI:
wbsp status my-crm --tenant acme-corpOutput:
Name: my-crm
Tenant: acme-corp
Image: myregistry/crm:v2.1.0
Status: running
Routes: http://crm.acme.com/
Database: acme_corp_my_crm (port 5432)API: (platform endpoint — see REST API Reference)
curl "$WBSP_API/api/v1/apps/acme-corp/my-crm"List All Your Applications
CLI:
wbsp list --provider compose --tenant acme-corpAPI: (platform endpoint — see REST API Reference)
curl "$WBSP_API/api/v1/apps?tenant=acme-corp"Application Status Values
| Status | Meaning |
|---|---|
pending | Deployment registered, not yet started |
deploying | Container and resources being provisioned |
running | Application is up and serving traffic |
dev | A dev destination is up — data services run in containers; you run the app from your IDE |
failed | Deployment or startup failed (check logs) |
removing | Application is being torn down |
removed | Application and all resources cleaned up |
CLI Reference
wbsp deploy
Deploy an application from a YAML configuration file.
wbsp deploy [--config <file>] --destination <name> [flags]| Flag | Required | Description |
|---|---|---|
--config | No | Path to the YAML configuration file (default: wbsp.yaml in the current directory) |
--destination | Yes¹ | Destination name as declared under destination:; selects the placement. Its type: is compose, dev, standalone, or aws (with a mode:) |
--verbose | No | Enable verbose output |
¹ --destination is optional when the config declares exactly one destination;
with more than one it is required. The selected destination's .env.<destination>
beside the yaml supplies external config. The lifecycle commands (stop,
status, logs, remove) scope by --enclave instead (auto-detected from
deployment state — see those commands below).
Note: every config must declare a top-level
destination:block.
Example:
wbsp deploy --config wbsp.yaml --destination dev
wbsp deploy --config my-app.yaml --destination prod
wbsp deploy --config demo-app.yaml --destination demowbsp stop
Stop a running application while preserving its data (database and Redis). Use
this to pause a local-machine deployment — including a dev destination's data
services — without deleting anything. Re-run wbsp deploy to bring it back.
wbsp stop <app-name> [--tenant <tenant>] [--enclave <name>] [flags]| Flag | Required | Description |
|---|---|---|
--tenant | No | Tenant scope (resolved from deployment state if omitted) |
--enclave | No | Enclave to stop (resolved from deployment state; required only to disambiguate same-name deployments) |
Example:
wbsp stop my-crm --tenant acme-corpTo tear down and delete the data, use wbsp remove instead.
wbsp list
List all deployed applications, optionally filtered by tenant.
wbsp list --provider <provider> [flags]| Flag | Required | Description |
|---|---|---|
--provider | Yes | Provider/type to list: a local-machine type (compose, dev, standalone) or aws; or an AWS deployment type to scope to (aws.on-demand, aws.demo) |
--tenant | No | Filter results to a specific tenant |
--json | No | Output as JSON |
Example:
wbsp list --provider compose --tenant acme-corpwbsp status
Show the status of a specific application.
wbsp status <app-name> [--tenant <tenant>] [--enclave <name>] [flags]| Flag | Required | Description |
|---|---|---|
--tenant | No | Tenant scope |
--enclave | No | Enclave to scope to (auto-detected; required only to disambiguate same-name deployments) |
--json | No | Output as JSON |
Example:
wbsp status my-crm --tenant acme-corp --jsonwbsp logs
Retrieve or stream logs from an application.
wbsp logs <app-name> [--tenant <tenant>] [--enclave <name>] [flags]| Flag | Required | Description |
|---|---|---|
--tenant | No | Tenant scope |
--enclave | No | Enclave to scope to (auto-detected; required only to disambiguate same-name deployments) |
--follow | No | Stream logs continuously (like tail -f) |
--lines | No | Number of recent lines to retrieve (default: 100) |
Example:
wbsp logs my-crm --tenant acme-corp --followwbsp remove
Remove an application and clean up all associated resources (container, routes, database).
wbsp remove <app-name> --tenant <tenant> [--enclave <name>] [flags]| Flag | Required | Description |
|---|---|---|
--tenant | Yes | Tenant that owns the application |
--enclave | No | Enclave to remove (resolved from deployment state; required only to disambiguate same-name deployments) |
--force | No | Skip the confirmation prompt |
Example:
wbsp remove my-crm --tenant acme-corp --forceOn success, outputs a JSON result confirming what was cleaned up:
{
"name": "my-crm",
"tenant": "acme-corp",
"container_removed": true,
"routes_removed": true,
"database_dropped": true
}REST API Reference
For most application creators the wbsp CLI is all you need — it is
self-contained and does not talk to a local server. The platform also
exposes an HTTP API for programmatic/CI use. It lives at the platform API
endpoint your operator gives you (there is no localhost API server to start);
the examples below use $WBSP_API as that base URL:
export WBSP_API="https://<platform-api-endpoint>" # ask your platform operatorAll endpoints accept and return JSON.
POST /api/v1/apps
Deploy a new application.
Request:
{
"name": "my-crm",
"tenant": "acme-corp",
"image": "myregistry/crm:v2.1.0",
"routes": [
{ "domain": "crm.acme.com", "path_prefix": "/" }
],
"database": { "enabled": true },
"resources": { "replicas": 1 },
"env": { "APP_ENV": "production" }
}Responses:
| Status | Meaning |
|---|---|
202 Accepted | Deployment started — response includes application details |
400 Bad Request | Configuration validation failed — response includes error details |
409 Conflict | Route conflict — another application already uses this domain/path |
Example:
curl -X POST $WBSP_API/api/v1/apps \
-H "Content-Type: application/json" \
-d '{"name":"my-crm","tenant":"acme-corp","image":"nginx:alpine","routes":[{"domain":"crm.local"}]}'GET /api/v1/apps
List all deployed applications, optionally filtered by tenant.
Query parameters:
| Parameter | Required | Description |
|---|---|---|
tenant | No | Filter by tenant identifier |
Response: 200 OK
{
"applications": [
{
"name": "my-crm",
"tenant": "acme-corp",
"status": "running",
"routes": ["http://crm.acme.com/"]
}
]
}Example:
curl "$WBSP_API/api/v1/apps?tenant=acme-corp"GET /api/v1/apps/{tenant}/{name}
Get the full status of a specific application.
Path parameters:
| Parameter | Description |
|---|---|
tenant | Tenant identifier |
name | Application name |
Responses:
| Status | Meaning |
|---|---|
200 OK | Application found — response includes full details |
404 Not Found | Application does not exist in the specified tenant |
Example:
curl $WBSP_API/api/v1/apps/acme-corp/my-crmPUT /api/v1/apps/{tenant}/{name}
Update and redeploy an existing application. The platform removes the old deployment and creates a new one with the updated configuration. The name and tenant in the request body must match the URL parameters.
Request: Same format as POST /api/v1/apps.
Responses:
| Status | Meaning |
|---|---|
202 Accepted | Redeployment started |
400 Bad Request | Validation error or name/tenant mismatch |
404 Not Found | Application does not exist |
Example:
curl -X PUT $WBSP_API/api/v1/apps/acme-corp/my-crm \
-H "Content-Type: application/json" \
-d '{"name":"my-crm","tenant":"acme-corp","image":"myregistry/crm:v3.0.0","routes":[{"domain":"crm.acme.com"}]}'DELETE /api/v1/apps/{tenant}/{name}
Remove an application and clean up all resources.
Responses:
| Status | Meaning |
|---|---|
202 Accepted | Removal started — response confirms what was cleaned up |
404 Not Found | Application does not exist |
Example:
curl -X DELETE $WBSP_API/api/v1/apps/acme-corp/my-crmGET /api/v1/platform/health
Check the health of the platform. This is useful for verifying the platform is ready before deploying.
Responses:
| Status | Meaning |
|---|---|
200 OK | Platform is healthy |
503 Service Unavailable | Platform is degraded — check the components array for details |
Response:
{
"platform_status": "healthy",
"components": [
{ "component": "database", "status": "healthy", "message": "connected" },
{ "component": "ingress", "status": "healthy", "message": "traefik responding" },
{ "component": "api", "status": "healthy", "message": "self-check passed" }
]
}Example:
curl $WBSP_API/api/v1/platform/healthLogs and Debugging
Viewing Logs
CLI:
# Last 100 lines
wbsp logs my-crm --tenant acme-corp
# Last 500 lines
wbsp logs my-crm --tenant acme-corp --lines 500
# Stream logs in real time
wbsp logs my-crm --tenant acme-corp --followTroubleshooting Checklist
If your application is not working as expected, work through these steps in order:
1. Check application status
wbsp status my-crm --tenant acme-corpIf the status is failed, the deployment did not complete. Check the deployment output for errors.
2. Check platform health
curl $WBSP_API/api/v1/platform/healthIf any platform component is unhealthy, contact your platform operator — the issue is with the platform, not your application.
3. Check application logs
wbsp logs my-crm --tenant acme-corp --lines 200Look for startup errors, crash traces, or connection failures.
4. Verify your configuration
Re-read your YAML config file and check:
- Is the image name correct and accessible?
- Is the domain name correct?
- If you declared a database, is your application using the injected
DATABASE_*environment variables?
5. Verify routing
For local deployments, ensure your /etc/hosts file maps the domain to 127.0.0.1. If you're seeing a "route conflict" error, another application is already using that domain and path prefix.
Common Error Patterns
| Symptom | Likely Cause | Fix |
|---|---|---|
Status shows failed | Image not found or application crashes on startup | Check image name; run docker run locally to debug |
409 Conflict on deploy | Another app uses the same domain/path | Choose a different domain or path prefix |
| Application runs but not accessible | Missing /etc/hosts entry (local) | Add 127.0.0.1 yourdomain.com to /etc/hosts |
| Database connection refused | App not reading injected env vars | Use DATABASE_HOST, DATABASE_PORT, etc. from the environment |
dev destination: app can't reach the database | App not run with the generated .env | source .env (or let your framework load it) before running the app from your IDE — it uses localhost and the derived host ports |
dev destination: connection refused on the DB port | Data-service containers not up | Re-run wbsp deploy --destination <dev-destination> to bring up Postgres/Redis |
Health check shows degraded | Platform component issue | Contact your platform operator |
Contacting the Platform Team
This section is a placeholder. Real contact information will be added in a future update.
If you have exhausted the troubleshooting checklist and still need help:
- Email: TBD — platform team email will be listed here
- Chat: TBD — chat channel or Slack workspace will be listed here
- Issue tracker: TBD — link to issue tracker will be listed here
When reaching out, include:
- Your application name and tenant
- The output of
wbsp status <name> --tenant <tenant> - Recent logs (
wbsp logs <name> --tenant <tenant> --lines 200) - Your YAML configuration file (with any secrets redacted)