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 the wbsp.yaml field reference, see the Application Creator Guide and wbsp-yaml-reference.md.

Table of Contents

  1. Prerequisites
  2. Local-Machine Deployment
  3. AWS Platform Setup
  4. Health Monitoring
  5. Platform Administration
  6. Platform Teardown

Prerequisites

For Local Development

  • Docker (version 20.10 or later) and Docker Compose (v2)
  • The wbsp-platform and wbsp CLIs — see Installing the WBSP CLIs (operators: go install, GitHub Release, or make build from source)
  • At least 2 GB of free memory for platform containers

Verify Docker is available:

docker --version
docker compose version

For 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 --version

Local-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 local is 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:

TypeWhat runs locally
composeThe whole app stack in containers (app + per-app PostgreSQL/Redis).
devOnly the data services in containers; the app is run from the developer's IDE.
standaloneThe 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 region

Verify access:

aws sts get-caller-identity --profile your-profile

Step 2: Provision the Platform

wbsp-platform provision --provider aws

This validates AWS credentials via aws sts get-caller-identity, then runs Terraform to create:

ResourcePurpose
VPC with public/private subnetsNetwork isolation across availability zones
EKS cluster with managed node groupsContainer orchestration with VPC CNI NetworkPolicy support
RDS PostgreSQL instanceManaged database service (private subnets, SSL required)
Traefik ingress controller (via Helm)Traffic routing via IngressRoute CRDs
ECR registryContainer 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 --json

The 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 OutputUsed For
eks_cluster_nameKubernetes API authentication
eks_cluster_endpointKubernetes API server URL
eks_cluster_caCluster certificate authority
rds_endpointDatabase host for app provisioning
rds_username / rds_passwordDatabase admin credentials
ecr_registry_urlContainer 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:

RolePurpose
<cluster>-lambda-executionExecution role for application Lambdas. Grants AWSLambdaBasicExecutionRole, VPC access, and ECR image pull.
<cluster>-demo-cleanupExecution 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 OutputUsed For
lambda_execution_role_arnIAM role assumed by application Lambda functions
lambda_security_group_idVPC security group attached to Lambda functions
lambda_subnet_idsPrivate subnets where Lambda functions run
demo_cleanup_role_arnIAM 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:

VariableOverrides
WBSP_LAMBDA_ROLE_ARNlambda_execution_role_arn
WBSP_LAMBDA_SG_IDlambda_security_group_id
WBSP_LAMBDA_SUBNET_IDSlambda_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:

EnvironmentExample ValueResulting URL Pattern
Local developmentdemo.localhosthttp://{token}.{app}.demo.localhost/
Productiondemo.wbsp.iohttp://{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

SymptomLikely CauseFix
exec plugin cannot support interactive modeOutdated client-go or missing InteractiveModeEnsure you're using the built binary (not an older version)
ErrImagePull on pod startupImage not in ECR or architecture mismatchVerify source/dockerfile in app config; platform builds linux/amd64
Database connection refusedApp not using SSLEnsure your app handles DATABASE_SSL=true (see Application Creator Guide)
permission denied for schema publicPostgreSQL 15+ schema permissionsPlatform handles this automatically; check RDS admin credentials
Namespace not found on route creationDeploy ordering issueEnsure 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 --json

Human-readable output:

Platform Status: healthy

COMPONENT            STATUS     MESSAGE
---------            ------     -------
database             healthy    connected
ingress              healthy    traefik responding

API 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

StatusMeaning
healthyComponent is running and responding normally
degradedComponent is partially functional or unreachable

What to Check When a Component Is Degraded

ComponentCheck
databaseIs RDS reachable? Check the RDS instance status and security groups, and wbsp-platform health --provider aws.
ingressIs Traefik running in the cluster? Check the Traefik pods/IngressRoutes in EKS.
apiIs 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 --json

The 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:deploy role in that tenant. There is exactly one other role, wbsp-platform:config-read, a lesser inspect-only grant. There is no wbsp-platform:admin.
  • Deploy in wbsp is 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 in wbsp are 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 authz with stable outcome and reason fields, so a rise in one refusal reason is countable without reading messages.
  • The recovery path is untouched. --backdoor bypasses 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 32

Delivery 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-everything flow (or any pg_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_KEY out 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=disabled refuses 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-config cluster 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 of specs/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 push to the wbsp-wbsp-api repo, then patch the deployment's restartedAt annotation. 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 --force

This runs terraform destroy to remove all AWS infrastructure. This is irreversible and will delete all data including RDS databases.

What Gets Removed

ResourceLocalAWS
Application containersYesYes (EKS pods)
Platform containersYesYes (EKS cluster)
Databases and dataYes (Docker volumes)Yes (RDS instance)
Network resourcesYes (Docker network)Yes (VPC, subnets, NAT)
Load balancersYes (Traefik container)Yes (ALB/NLB)
Lambda functions and IAM rolesN/AYes (Lambda, IAM, EventBridge)

Troubleshooting

If platform destroy reports errors:

  1. Check if containers are still running: docker ps | grep wbsp
  2. Force-remove stuck containers: docker rm -f <container-name>
  3. Remove the Docker network manually if needed: docker network rm wbsp
  4. Remove orphaned volumes: docker volume prune

CLI Reference for Platform Operators

wbsp-platform provision

wbsp-platform provision --provider <provider> [flags]
FlagRequiredDescription
--providerYesPlatform provider/environment: local or aws
--configNoPath to a platform configuration file
--verboseNoEnable verbose output

wbsp-platform health

wbsp-platform health --provider <provider> [flags]
FlagRequiredDescription
--providerYesPlatform provider/environment: local or aws
--jsonNoOutput as JSON

wbsp-platform destroy

wbsp-platform destroy --provider <provider> [flags]
FlagRequiredDescription
--providerYesPlatform provider/environment: local or aws
--forceNoSkip the confirmation prompt