WBSP Platform Operator Guide
Audience: Platform operators who set up, administer, and maintain the WBSP platform. You should have basic experience with Docker and Docker Compose (for local) or AWS (for cloud).
Not what you're looking for? If you want to deploy an application on the platform, see the Application Creator Guide.
Application placement: application configs use a
destination:map (placement: cluster + enclave + mode). The operator-owned concepts here — clusters and which one receives edge traffic by default, plus diverting specific wildcard domains/vanity hosts to additional clusters — are an operator decision, never an app-owner one. For thewbsp.yamlfield reference, see the Application Creator Guide and wbsp-yaml-reference.md.
Table of Contents
- Prerequisites
- Local-Machine Deployment
- AWS Platform Setup
- Health Monitoring
- Platform Administration
- Platform Teardown
Prerequisites
For Local Development
- Docker (version 20.10 or later) and Docker Compose (v2)
- The
wbsp-platformandwbspCLIs — see Installing the WBSP CLIs (operators:go install, GitHub Release, ormake buildfrom source) - At least 2 GB of free memory for platform containers
Verify Docker is available:
docker --version
docker compose versionFor AWS Deployment
Everything listed above for local, plus:
- AWS CLI (v2) configured with valid credentials
- Terraform (v1.5 or later)
- An AWS account with permissions to create: VPCs, EKS clusters, RDS instances, load balancers, IAM roles
- A target AWS region selected
Verify AWS access:
aws sts get-caller-identity
terraform --versionLocal-Machine Deployment (no platform to provision)
The local platform is retired (feature 063). There is no local Traefik, PostgreSQL, or WBSP-API to provision —
wbsp-platform --provider localis gone (it returns an error). Running apps on a laptop no longer requires an operator to stand up a shared local platform.
Local-machine application deployment is now fully self-contained and is an
application-creator activity, not a platform-operator one. An app creator picks a
local-machine destination type in wbsp.yaml and deploys with wbsp:
| Type | What runs locally |
|---|---|
compose | The whole app stack in containers (app + per-app PostgreSQL/Redis). |
dev | Only the data services in containers; the app is run from the developer's IDE. |
standalone | The app and its data services inside a single ephemeral container. |
None use Traefik; each host-publishes its own ports. The only one-time host setup is
docker network create wbsp-shared (for the compose type's HAP reachability). See
the Application Creator Guide for the full workflow.
The rest of this guide covers the AWS platform, which operators still provision and manage.
{
"platform_status": "healthy",
"components": [
{ "component": "database", "status": "healthy", "message": "connected" },
{ "component": "ingress", "status": "healthy", "message": "traefik responding" }
]
}(That shape is the AWS wbsp-platform health --provider aws --json output.)
AWS Platform Setup
Step 1: Configure AWS Environment
Set the AWS profile and region for your deployment:
export AWS_PROFILE=your-profile # named profile with EKS/RDS/ECR permissions
export AWS_REGION=ap-southeast-1 # your target regionVerify access:
aws sts get-caller-identity --profile your-profileStep 2: Provision the Platform
wbsp-platform provision --provider awsThis validates AWS credentials via aws sts get-caller-identity, then runs Terraform to create:
| Resource | Purpose |
|---|---|
| VPC with public/private subnets | Network isolation across availability zones |
| EKS cluster with managed node groups | Container orchestration with VPC CNI NetworkPolicy support |
| RDS PostgreSQL instance | Managed database service (private subnets, SSL required) |
| Traefik ingress controller (via Helm) | Traffic routing via IngressRoute CRDs |
| ECR registry | Container image storage (wbsp-<app> repositories) |
| Load balancer (ALB/NLB) | Public internet access |
Provisioning takes 15-25 minutes depending on region and resource availability.
Step 3: Verify Health
wbsp-platform health --provider aws --jsonThe health check verifies connectivity to the EKS cluster, RDS instance, and Traefik ingress. All components must report healthy before applications can be deployed.
How AWS Configuration Works
After provisioning, the CLI reads Terraform outputs automatically (terraform output -json from deploy/aws/). You do not need to manually set environment variables for RDS endpoints, EKS cluster details, or ECR registry URLs — these are resolved at runtime.
The following environment variables are read from Terraform outputs:
| Terraform Output | Used For |
|---|---|
eks_cluster_name | Kubernetes API authentication |
eks_cluster_endpoint | Kubernetes API server URL |
eks_cluster_ca | Cluster certificate authority |
rds_endpoint | Database host for app provisioning |
rds_username / rds_password | Database admin credentials |
ecr_registry_url | Container image push target |
The only environment variables you must set yourself are AWS_PROFILE (or AWS credentials) and AWS_REGION.
Lambda Deployment Prerequisites
Lambda support is provisioned automatically as part of wbsp-platform provision --provider aws. The Terraform module at deploy/aws/modules/lambda/main.tf creates all required resources.
IAM roles created by Terraform:
| Role | Purpose |
|---|---|
<cluster>-lambda-execution | Execution role for application Lambdas. Grants AWSLambdaBasicExecutionRole, VPC access, and ECR image pull. |
<cluster>-demo-cleanup | Execution role for the wbsp-demo-cleanup Lambda. Grants permissions to delete expired demo Lambdas, their IAM roles, EventBridge schedules, and CloudWatch log groups. |
Security group:
A security group (<cluster>-lambda-rds) is created in the platform VPC to allow Lambda functions to reach the RDS instance on port 5432, DNS, and HTTPS for AWS API calls.
Terraform outputs for Lambda:
| Terraform Output | Used For |
|---|---|
lambda_execution_role_arn | IAM role assumed by application Lambda functions |
lambda_security_group_id | VPC security group attached to Lambda functions |
lambda_subnet_ids | Private subnets where Lambda functions run |
demo_cleanup_role_arn | IAM role for the demo cleanup Lambda |
Like the other AWS outputs, these are read automatically via terraform output -json. You can override them with environment variables if needed:
| Variable | Overrides |
|---|---|
WBSP_LAMBDA_ROLE_ARN | lambda_execution_role_arn |
WBSP_LAMBDA_SG_ID | lambda_security_group_id |
WBSP_LAMBDA_SUBNET_IDS | lambda_subnet_ids (comma-separated) |
Sample mode base domain:
Sample-mode deployments (an aws destination with mode: demo) use subdomain-based routing. The sample_base_domain is configured at platform provisioning time (Terraform variable) or via the WBSP_SAMPLE_BASE_DOMAIN environment variable:
| Environment | Example Value | Resulting URL Pattern |
|---|---|---|
| Local development | demo.localhost | http://{token}.{app}.demo.localhost/ |
| Production | demo.wbsp.io | http://{token}.{app}.demo.wbsp.io/ |
For production, configure a wildcard DNS record (*.demo.wbsp.io) pointing to the Traefik load balancer, and optionally an ACM wildcard certificate (*.demo.wbsp.io) for HTTPS. For local development, add specific subdomain entries to /etc/hosts after deploying, or use dnsmasq with address=/demo.localhost/127.0.0.1 for wildcard resolution.
Demo cleanup Lambda:
When you first deploy an application to the reserved demo (or sandbox) destination, the platform automatically creates a wbsp-demo-cleanup Lambda. This function runs on an EventBridge Scheduler schedule to delete expired demo deployments. The cleanup Lambda uses the demo_cleanup_role_arn role, which is scoped to resources prefixed with wbsp-.
AWS Troubleshooting
| Symptom | Likely Cause | Fix |
|---|---|---|
exec plugin cannot support interactive mode | Outdated client-go or missing InteractiveMode | Ensure you're using the built binary (not an older version) |
ErrImagePull on pod startup | Image not in ECR or architecture mismatch | Verify source/dockerfile in app config; platform builds linux/amd64 |
| Database connection refused | App not using SSL | Ensure your app handles DATABASE_SSL=true (see Application Creator Guide) |
permission denied for schema public | PostgreSQL 15+ schema permissions | Platform handles this automatically; check RDS admin credentials |
| Namespace not found on route creation | Deploy ordering issue | Ensure you're using the latest binary — namespace is created before routes |
Health Monitoring
CLI Health Check
# Human-readable output
wbsp-platform health --provider aws
# JSON output for scripting
wbsp-platform health --provider aws --jsonHuman-readable output:
Platform Status: healthy
COMPONENT STATUS MESSAGE
--------- ------ -------
database healthy connected
ingress healthy traefik respondingAPI Health Check
The AWS API server (wbsp-api) exposes a health endpoint at $WBSP_API_URL:
curl "$WBSP_API_URL/api/v1/platform/health"Returns HTTP 200 when healthy, HTTP 503 when degraded. The response body lists each component's status.
Component Status Values
| Status | Meaning |
|---|---|
healthy | Component is running and responding normally |
degraded | Component is partially functional or unreachable |
What to Check When a Component Is Degraded
| Component | Check |
|---|---|
| database | Is RDS reachable? Check the RDS instance status and security groups, and wbsp-platform health --provider aws. |
| ingress | Is Traefik running in the cluster? Check the Traefik pods/IngressRoutes in EKS. |
| api | Is the wbsp-api deployment healthy in the cluster? Check its pods and logs (kubectl logs). |
Platform Inventory
wbsp-platform inventory shows, in a single read-only table, everything the
platform runs on a target: application workloads across every namespace, the
platform-system infrastructure, parallel services environments, and the managed
data tier (databases and Redis). It answers "what is running here?" without
running a separate kubectl per namespace.
# Full inventory for a target (for AWS this aggregates EKS + on-demand + demo)
wbsp-platform inventory --provider aws
# Filter to one tenant (applies to workloads, data tier, and parallel envs)
wbsp-platform inventory --provider aws --tenant wbsp
# Machine-readable output for scripts
wbsp-platform inventory --provider aws --jsonThe table is grouped into sections — Platform system, Applications, Parallel environments, and Data tier — with each row showing the namespace, name, kind (Deployment / StatefulSet / Lambda / Database / Redis / ParallelService), exposure (service kind and port(s)), tenant, live status (ready/desired, age, restart and health markers), and a short description.
Notes:
- AWS aggregation: any AWS target flag (
aws,aws.on-demand,aws.demo) returns the same aggregated view across all three sub-targets; each Lambda row is tagged with its sub-target. - Read-only: the command never creates, modifies, or removes anything.
- No secrets: output contains names, ports, and roles only — never passwords or connection strings.
- Partial results: if one source is unavailable, the rest is still shown and the failed source is listed in a footer; the command exits non-zero only when the target is wholly unreachable.
Platform Administration
Who May Deploy (feature 093)
The platform holds no record of who may act on which tenant, and no
administrator or operator concept of its own. Both were deleted rather than
relocated. Authority is read from the sign-in credential, and one rule applies in
every tenant — including wbsp, where the platform's own applications live:
A credential carries the deploy verb for the tenant that issued it, or it does not. A credential issued for one tenant confers nothing in any other, because only that tenant's keys verify it.
What that means in practice:
- Tenants and roles are administered in HAP, by each tenant's own
administrator, in HAP's console or API. Granting a colleague working access is
one act: the
wbsp-platform:deployrole in that tenant. There is exactly one other role,wbsp-platform:config-read, a lesser inspect-only grant. There is nowbsp-platform:admin. - Deploy in
wbspis a privileged grant — the platform's own API is an installation there, and deploy authority is tenant-wide — so it is granted rarely and deliberately. The many people registered inwbspare users of its applications; they hold no deploy role and should not. - Administering HAP grants nothing on the platform by itself. Someone who
needs to act on a tenant grants themselves the deploy role there and signs in
to it (
wbsp login --tenant <name>), leaving a grant record in the tenant they touched. There is no cross-tenant override. - Refusals name the remedy: the tenant, the missing role, and the administration page of that tenant on this installation's provider. A tenant that does not exist, a credential for another tenant, and an unreachable provider each read differently — and the last is never a permissions problem.
- Every authorisation decision is logged under component
authzwith stableoutcomeandreasonfields, so a rise in one refusal reason is countable without reading messages. - The recovery path is untouched.
--backdoorbypasses the platform and drives the cloud with your own credentials; it has no dependency on the identity provider and must never acquire one.
Configuration is a single value, WBSP_HAP_BASE_URL. Bringing a new tenant into
service needs no change to it and no restart.
There is a second, optional value for one situation, and it is worth being clear
that the situation is rare: WBSP_HAP_DISCOVERY_BASE_URL. A well-built
installation publishes an identity provider whose issuer resolves from
everywhere — from a browser and from inside the platform's own network — and
needs nothing here. Leave it unset, including on the AWS platform.
Set it only where that is genuinely not true: where the provider is reachable
by the platform process at a different address than the one its issuers name.
Then it holds the address the platform can reach, WBSP_HAP_BASE_URL keeps
naming the published one, verification keys are fetched from the first, and
credentials are still checked against the second. Key material is only ever
fetched from one of those two configured addresses; a provider naming a key set
at a third is refused.
The point of the setting is what it makes unnecessary. Faced with an unreachable issuer, the tempting fix is to rewrite the issuer to the address that works — which makes every token that installation mints valid only on the network it was bent to fit. Do not do that; set this instead.
Environment Variables
AWS platform behaviour is configured via the WBSP_* / AWS_* variables read by
wbsp-platform and wbsp-api (RDS, ECR, EKS, and ACME settings). See
AWS Platform Setup for the required ones.
The Configuration Store Encryption Key (feature 091)
Every stored configuration value and every retained pull credential is sealed
with AES-256-GCM under WBSP_CONFIG_ENCRYPTION_KEY — base64 of exactly 32
random bytes:
openssl rand -base64 32Delivery follows the platform's other durable secrets:
- Cloud platform: in the wbsp-api deployment's Secret, beside
WBSP_RDS_PASSWORD(same delivery, same rotation posture). - Appliance: through the compose environment for the wbsp-api service, sourced from OUTSIDE the backed-up filesystem (an OS keychain or equivalent). A key that rides in the same backup as the database makes the encryption decorative.
Without the key the platform still boots and serves everything else: the
config store refuses reads and writes with a sentence naming the variable,
the boot log carries one WARN, and /api/v1/healthz reports
capabilities.config_store: "no_key". At boot with a valid key the log
states the key id (an 8-character hash prefix — the key itself is never
logged, returned, or stored in the database it protects).
Configuration Store and Backups (feature 091)
The installation configuration store and the shared-enclave registry live in the platform's Postgres database — the same database as users, tokens, the deploy registry and hosting records — on the cloud platform and the appliance alike. Consequences an operator can rely on:
- They ride the ordinary database backup with no extra step. The
wbsp-backup-everythingflow (or anypg_dump) now contains every installation's configuration, retained pull credentials (sealed), and enclave placements. - On an appliance, confirm your backup set actually reaches the Postgres
volume — do not assume it does. The store rides whatever backup covers
the platform's own database, and on an appliance that is wherever the
compose stack put it. Where Postgres is a named docker volume inside a VM
whose disk is deliberately outside the backup set (the wbsp-vm appliance is
exactly this — a Lima VM treated as destroy-and-rebuild), NO backup
contains the store: it survives restarts and rebuild-in-place, not
destruction of the VM. Custody stays honest for free there (data and key
are never in one backup set because the data is in no backup set), but if
the platform's own database ever DOES join a backup set, move
WBSP_CONFIG_ENCRYPTION_KEYout of that set first. - A lost store costs more than re-typed values. The enclave placement and the registered sign-in client id are RECORDED facts, not derived ones. With no recorded placement and no live deployment, a redeploy naming no enclave defaults to the installation's OWN enclave (the app name) and records that — so an app that used to share an enclave silently lands in a new private one, and application data restored from a separate backup is then bound to the wrong database. Shared-enclave REGISTRATIONS live in the same store, so recovery is ordered: re-register the enclave, redeploy naming it explicitly, then restore the application data.
- That hazard needs a deployer, and arrives with one. Placement is
recorded only by the deploy-image path, which
WBSP_API_DEPLOYER=disabledrefuses with 503 before resolving anything. A deployer-less appliance that installs locally under a deterministic name (the application's own name, as the compose provider does) records no placement and so has none to lose: a rebuilt machine re-installs to the identical database name, and a host-side dump restores where the application already points. Whoever gives an appliance a deployer hands it this hazard in the same release — publish the ordering above alongside that capability, not after it. - Cluster teardown no longer loses configuration. The cluster is the destroyable half of the platform; the store no longer lives in it.
- A backup is diagnosable without the key — installation identity, mode,
provenance, value NAMES and placements are plaintext — but no VALUE and no
credential is recoverable from a backup without
WBSP_CONFIG_ENCRYPTION_KEY. Restoring onto a platform holding a different key reports the values as UNREADABLE (naming both key ids), never as absent. - The
wbsp-configcluster namespace is retired. Fresh platforms never create it. On the production platform it is the ROLLBACK PATH for the 091 cutover and must not be deleted except as the final, gated, operator-initiated step ofspecs/091-postgres-encrypted-stores/cutover-production.md.
Deploying wbsp-api Itself (platform-internal, non-catalog)
wbsp-api deploys from cmd/wbsp-api (its wbsp.yaml + .env.wbsp live
beside its source). Because it is a non_variant app, the API-routed deploy
records its configuration but CANNOT push its image: the registry gateway
derives paths from the git remote and has no repository for a non-catalog
repo (404). Two working paths:
- Full deploy:
wbsp deploy --destination wbsp --backdoor— direct ECR push + direct EKS, and the config record still lands (the record step is shared by both deploy paths). - Rollout of an already pushed image: build +
docker pushto thewbsp-wbsp-apirepo, then patch the deployment'srestartedAtannotation. Record the previous digest first — that digest IS the rollback.
The API-routed deploy is still useful on its own: it re-writes the
installation record from .env.wbsp (it fails at the image-push step
afterwards, harmlessly, having already recorded).
Viewing Platform Logs
The AWS platform's components run in the cluster; view their logs with kubectl:
kubectl logs -n <wbsp-api-namespace> deploy/wbsp-api
kubectl logs -n traefik deploy/traefik(There are no local platform containers to log — the local platform is retired, feature 063.)
Platform Teardown
There is no local platform to tear down (feature 063). Local-machine app deployments are removed per-app by their creator with
wbsp remove <app> --tenant <tenant>(see the Application Creator Guide).
AWS Teardown
wbsp-platform destroy --provider aws --forceThis runs terraform destroy to remove all AWS infrastructure. This is irreversible and will delete all data including RDS databases.
What Gets Removed
| Resource | Local | AWS |
|---|---|---|
| Application containers | Yes | Yes (EKS pods) |
| Platform containers | Yes | Yes (EKS cluster) |
| Databases and data | Yes (Docker volumes) | Yes (RDS instance) |
| Network resources | Yes (Docker network) | Yes (VPC, subnets, NAT) |
| Load balancers | Yes (Traefik container) | Yes (ALB/NLB) |
| Lambda functions and IAM roles | N/A | Yes (Lambda, IAM, EventBridge) |
Troubleshooting
If platform destroy reports errors:
- Check if containers are still running:
docker ps | grep wbsp - Force-remove stuck containers:
docker rm -f <container-name> - Remove the Docker network manually if needed:
docker network rm wbsp - Remove orphaned volumes:
docker volume prune
CLI Reference for Platform Operators
wbsp-platform provision
wbsp-platform provision --provider <provider> [flags]| Flag | Required | Description |
|---|---|---|
--provider | Yes | Platform provider/environment: local or aws |
--config | No | Path to a platform configuration file |
--verbose | No | Enable verbose output |
wbsp-platform health
wbsp-platform health --provider <provider> [flags]| Flag | Required | Description |
|---|---|---|
--provider | Yes | Platform provider/environment: local or aws |
--json | No | Output as JSON |
wbsp-platform destroy
wbsp-platform destroy --provider <provider> [flags]| Flag | Required | Description |
|---|---|---|
--provider | Yes | Platform provider/environment: local or aws |
--force | No | Skip the confirmation prompt |