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

  1. Using the /wbsp-build skill
  2. Architecting for the Platform
  3. Preparing Your Application
  4. Configuration
  5. Developing Outside the Platform
  6. Deploying Your Application
  7. Monitoring Your Application
  8. CLI Reference
  9. REST API Reference
  10. Logs and Debugging
  11. 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), or split (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:

VariableDescription
DATABASE_HOSTHostname of the provisioned Postgres instance
DATABASE_PORTPort number (typically 5432)
DATABASE_NAMEName of your application's database
DATABASE_USERUsername for database access
DATABASE_PASSWORDPassword for database access
DATABASE_SSLSet 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 | bash

You'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) destination

The 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 --backdoor path only (it needs AWS_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.0

Visit 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

FieldTypeDescription
variantstring (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.
namestringDNS-safe application name ([a-z0-9][a-z0-9-]*)
image or dockerfile+sourcestringDocker image reference (e.g., myregistry/my-app:v1.0.0), or how to build it
destinationmapOne or more named placements, each declaring its own tenant (see below)

The tenant is declared per-destination (under each entry of destination:), never at the document root — a root-level tenant: 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.yaml

This 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-hh70

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

FieldTypeDescription
sourcestringPath to application source directory (relative to repo root)
dockerfilestringPath to Dockerfile (relative to repo root)

Destinations, components, enclaves

A config splits where an app runs from what runs:

  • destinationwhere the app runs (placement only). A destination has a type (compose, dev, standalone, or aws) and, for aws, a mode; an aws destination also names the cluster and enclave it lands in. The local-machine types (compose/dev/standalone) need neither.
  • componentwhat runs. A single-component app declares image/dockerfile/port/access/routes at the top level (this is the one unnamed component); a multi-component app lists each workload under a components: map.

Destinations

Each key under destination: is a placement name you choose (dev, demo, prod, …) — it becomes the --destination value at deploy time.

FieldTypeApplies toDescription
typestringallcompose, dev, standalone, or aws
modestringawsnormal (aws default), on-demand, gvisor, katademo is reserved to the demo/sandbox destinations and may not be declared
clusterstringawsCluster this placement targets (required for aws)
enclavestringawsApplication-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 accesspublic (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 destination

Worked 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) destination

Note 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-level database.enabled / redis.enabled / s3.enabled blocks (the resources: { database, redis, s3 } shape has been removed).

Route Entry

FieldTypeDefaultDescription
domainstringrequiredFully qualified domain name
path_prefixstring/URL path prefix for sub-routing
strip_prefixbooleantrueStrip the path prefix before forwarding to your app (see Path Prefixes)

Optional Fields

database

FieldTypeDefaultDescription
enabledbooleanfalseProvision a dedicated PostgreSQL database
shared_withstringShare another app's database instead of provisioning a new one
extensionslist of stringPostgreSQL 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 on aws (RDS), on the dev/compose containers, and in the bundled engine used by standalone, demo and sandbox. postgis is also available: declare it and the bundled engine used by standalone, demo and 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 the wbsp.yaml reference, 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 EXTENSION requires superuser — and your application's database role is not one, on any destination. On demo/sandbox/standalone this bites hardest, because your sample data is loaded as that role: pg_dump emits CREATE EXTENSION IF NOT EXISTS vector whenever 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

FieldTypeDefaultDescription
resources.replicasinteger1Fixed instance count for the single unnamed component
components.<n>.replicasinteger1Fixed instance count for a named component (mutually exclusive with autoscale)
components.<n>.autoscalemap{ min, max, targetCPU } (named components only)

redis

FieldTypeDefaultDescription
enabledbooleanfalseProvision a managed Redis (injects REDIS_*, including REDIS_NAMESPACE and REDIS_TLS)
durablebooleanfalsefalse = cache mode (ElastiCache; treat as ephemeral). true = persistent store (MemoryDB) for data that must survive a restart

s3

FieldTypeDefaultDescription
enabledbooleanfalseProvision 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, and s3.enabled blocks. (The old resources: { database, redis, s3 } block has been removed — use the .enabled blocks.)

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

Example: 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 CRM

On 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 Two

The 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: production

Connecting 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: any opens it to any tenant; access: public to 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 prefix

On 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 to any.
  • 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:

  1. Provisions your database and Redis (if declared in config) in containers
  2. Host-publishes their ports, derived from your project number (Postgres 54NNN, Redis 63NNN)
  3. Writes a .env next to your app with discrete DATABASE_* / REDIS_* values pointing at localhost on 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 dev

Then 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 environment

Most 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 dev

Stopping and Tearing Down

Stop (preserve the database and Redis data):

wbsp stop my-crm --tenant acme-corp

Remove (tear down and delete the data):

wbsp remove my-crm --tenant acme-corp --force

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

Variabledev Valueaws Value
DATABASE_HOSTlocalhostRDS hostname
DATABASE_PORTDerived host port (54NNN)5432
DATABASE_NAMESameSame
DATABASE_USERSameSame
DATABASE_PASSWORDSameSame
REDIS_HOSTlocalhostElastiCache hostname
REDIS_PORTDerived 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 dev type 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 wbsp CLI 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 aws destination 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 dev

Add --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 wbsp CLI 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-corp

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

AspectcomposedevstandaloneAWS (always-on)AWS (on-demand)AWS Sample
Destination type / modetype: composetype: devtype: standalonetype: aws (mode: normal)type: aws, mode: on-demandreserved name demo — declare demo: {}, no type/mode
What runsApp + DB + Redis containersDB + Redis containers; app from your IDEApp + DB + Redis in one containerECR (auto-built)ECR + Lambda Web AdapterEphemeral EKS pod: app + bundled Postgres+Redis (gVisor)
Image sourceLocal Docker imagesLocal Docker images (data services only)Local Docker imagesECR (auto-built)ECR + Lambda Web AdapterECR (prepared demo image)
DatabasePer-app local PostgresPer-app local PostgresEmbedded in the containerAmazon RDSShared RDSBundled in-pod Postgres (ephemeral)
ScalingFixed replicasn/a (run from IDE)Single containerFixed replicasScale-to-zeroScale-to-zero (per-launch pod)
Cold startNoneNoneNoneNone~10s (VPC)~30-60s (pod boot + seed)
CleanupManualManualManual (ephemeral)ManualManualAuto (configurable timeout)
Use caseProduction-parity localInner-loop developmentThrowaway local instanceProductionLow-traffic appsDemos 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:

  1. Publish (one-time per release)wbsp deploy --destination demo builds 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.
  2. 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).

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

VariableMeaning
AUTH_HAP_ISSUERDemo-tenant HAP issuer — discovery at ${AUTH_HAP_ISSUER}/.well-known/openid-configuration
AUTH_HAP_ID / AUTH_HAP_SECRETThe shared client, registered once with a *.wbsp-demo.com wildcard redirect
NEXT_PUBLIC_HAP_ENABLEDtrue — 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.demo to get past the check: the launch overlay only overrides keys with the same names, so an app reading its own OIDC_* 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:

FieldTypeDefaultDescription
sample_data.pathstringrequiredDirectory of standard .sql seed files baked into the demo image
sample_data.timeout_minutesint10Auto-cleanup timeout (instance TTL)
sample_data:
  path: examples/sample-data/crm
  timeout_minutes: 10

For local development, add your application's domain to /etc/hosts:

127.0.0.1  crm.acme.com

Universal 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 universal last. A cloud deploy of an app that declares universal: prints a reminder.
  • release: travels on the image. An appliance cannot read your wbsp.yaml; it reads the run manifest stamped on the image, built from this destination's effective configuration. An empty universal: {} inherits your top-level release: — override it under the block when the appliance needs something different. Unlike the serverless placements, universal accepts a release:; the appliance is precisely what runs it.
  • Login only. Publishing goes through the registry gateway with your wbsp login session and needs no AWS credentials. --backdoor is 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-corp

Output:

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

API: (platform endpoint — see REST API Reference)

curl "$WBSP_API/api/v1/apps?tenant=acme-corp"

Application Status Values

StatusMeaning
pendingDeployment registered, not yet started
deployingContainer and resources being provisioned
runningApplication is up and serving traffic
devA dev destination is up — data services run in containers; you run the app from your IDE
failedDeployment or startup failed (check logs)
removingApplication is being torn down
removedApplication and all resources cleaned up

CLI Reference

wbsp deploy

Deploy an application from a YAML configuration file.

wbsp deploy [--config <file>] --destination <name> [flags]
FlagRequiredDescription
--configNoPath to the YAML configuration file (default: wbsp.yaml in the current directory)
--destinationYes¹Destination name as declared under destination:; selects the placement. Its type: is compose, dev, standalone, or aws (with a mode:)
--verboseNoEnable 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 demo

wbsp 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]
FlagRequiredDescription
--tenantNoTenant scope (resolved from deployment state if omitted)
--enclaveNoEnclave to stop (resolved from deployment state; required only to disambiguate same-name deployments)

Example:

wbsp stop my-crm --tenant acme-corp

To 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]
FlagRequiredDescription
--providerYesProvider/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)
--tenantNoFilter results to a specific tenant
--jsonNoOutput as JSON

Example:

wbsp list --provider compose --tenant acme-corp

wbsp status

Show the status of a specific application.

wbsp status <app-name> [--tenant <tenant>] [--enclave <name>] [flags]
FlagRequiredDescription
--tenantNoTenant scope
--enclaveNoEnclave to scope to (auto-detected; required only to disambiguate same-name deployments)
--jsonNoOutput as JSON

Example:

wbsp status my-crm --tenant acme-corp --json

wbsp logs

Retrieve or stream logs from an application.

wbsp logs <app-name> [--tenant <tenant>] [--enclave <name>] [flags]
FlagRequiredDescription
--tenantNoTenant scope
--enclaveNoEnclave to scope to (auto-detected; required only to disambiguate same-name deployments)
--followNoStream logs continuously (like tail -f)
--linesNoNumber of recent lines to retrieve (default: 100)

Example:

wbsp logs my-crm --tenant acme-corp --follow

wbsp remove

Remove an application and clean up all associated resources (container, routes, database).

wbsp remove <app-name> --tenant <tenant> [--enclave <name>] [flags]
FlagRequiredDescription
--tenantYesTenant that owns the application
--enclaveNoEnclave to remove (resolved from deployment state; required only to disambiguate same-name deployments)
--forceNoSkip the confirmation prompt

Example:

wbsp remove my-crm --tenant acme-corp --force

On 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 operator

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

StatusMeaning
202 AcceptedDeployment started — response includes application details
400 Bad RequestConfiguration validation failed — response includes error details
409 ConflictRoute 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:

ParameterRequiredDescription
tenantNoFilter 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:

ParameterDescription
tenantTenant identifier
nameApplication name

Responses:

StatusMeaning
200 OKApplication found — response includes full details
404 Not FoundApplication does not exist in the specified tenant

Example:

curl $WBSP_API/api/v1/apps/acme-corp/my-crm

PUT /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:

StatusMeaning
202 AcceptedRedeployment started
400 Bad RequestValidation error or name/tenant mismatch
404 Not FoundApplication 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:

StatusMeaning
202 AcceptedRemoval started — response confirms what was cleaned up
404 Not FoundApplication does not exist

Example:

curl -X DELETE $WBSP_API/api/v1/apps/acme-corp/my-crm

GET /api/v1/platform/health

Check the health of the platform. This is useful for verifying the platform is ready before deploying.

Responses:

StatusMeaning
200 OKPlatform is healthy
503 Service UnavailablePlatform 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/health

Logs 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 --follow

Troubleshooting 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-corp

If 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/health

If 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 200

Look 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

SymptomLikely CauseFix
Status shows failedImage not found or application crashes on startupCheck image name; run docker run locally to debug
409 Conflict on deployAnother app uses the same domain/pathChoose a different domain or path prefix
Application runs but not accessibleMissing /etc/hosts entry (local)Add 127.0.0.1 yourdomain.com to /etc/hosts
Database connection refusedApp not reading injected env varsUse DATABASE_HOST, DATABASE_PORT, etc. from the environment
dev destination: app can't reach the databaseApp not run with the generated .envsource .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 portData-service containers not upRe-run wbsp deploy --destination <dev-destination> to bring up Postgres/Redis
Health check shows degradedPlatform component issueContact 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:

  1. Your application name and tenant
  2. The output of wbsp status <name> --tenant <tenant>
  3. Recent logs (wbsp logs <name> --tenant <tenant> --lines 200)
  4. Your YAML configuration file (with any secrets redacted)