Deploying to AWS without AWS credentials

Most developers do not have (and should not need) AWS credentials to deploy their app. wbsp deploy to an AWS destination works with only your platform login and local Docker — no --backdoor. Images are pushed through the platform's collaborator-gated container registry gateway (registry.wbsp.ai), the same mechanism you already use for git push.

One login sets everything up

wbsp login          # browser sign-in

wbsp login signs you in and registers wbsp as your git and docker credential helper for the platform's hosts. From then on, git push, docker push, and wbsp deploy all work off that one login — no AWS keys, no tokens to paste. Each push asks the helper for a freshly-issued token, so nothing static is stored in your keychain and short-lived tokens never go stale; the CLI silently renews your session from its refresh token, so you rarely sign in again (only when the session itself expires). wbsp logout clears it.

wbsp deploy --destination aws-prod

That's it: build runs locally (your source never leaves your machine), the image is pushed to registry.wbsp.ai/<owner>/<repo>/app (the same <owner>/<repo> as your git remote), the gateway checks you collaborate on that variant and forwards it to ECR, and wbsp-api rolls it out.

Two names, one image. registry.wbsp.ai/<owner>/<repo>/app is the gateway address you push to, keyed by your git <owner>/<repo>. The gateway resolves it to the variant's underlying ECR repositorywbsp-v-<short-uuid>-app, the variant-derived name documented in wbsp-yaml-reference.md — and rewrites the push there, so you never see or address the ECR id directly. Both are "the same repository git push uses" because your git remote and your catalog variant are bound together; the address is git-keyed, the storage is variant-keyed.

For the credential helpers to work, wbsp must be on your PATH — git runs wbsp git-credential by name, and docker runs a docker-credential-wbsp shim that wbsp login creates next to the wbsp binary. If you run a checked-out bin/wbsp, put that bin/ on PATH.

CI / automation — one secret, and what it can and cannot do today

A pipeline needs exactly one secret: WBSP_API_TOKEN, an access token minted by a service account at the identity provider — an application in the tenant the pipeline acts on, created by that tenant's administrator with the scopes the work needs (wbsp-platform:deploy for a pipeline that deploys). Mint it with the client-credentials grant at the start of each job; it serves one tenant and lives an hour by default, so mint per job rather than storing one:

export WBSP_API_TOKEN=$(curl -sS -X POST https://hap.wbsp.ai/t/<tenant>/oauth2/token \
  -d "grant_type=client_credentials&client_id=$CLIENT_ID&client_secret=$CLIENT_SECRET" \
  | jq -r .access_token)

The platform itself mints nothing (feature 093): wbsp token create and wbsp token revoke are gone, and so are the platform-signed CI tokens they produced. Revoke by disabling the service account at the provider — effective within one token lifetime. wbsp whoami reports the service account's identity while the variable is set.

What the token authenticates, stated separately, because they differ:

  • The deploy API — yes. WBSP_API_TOKEN is accepted everywhere a login session is accepted.
  • The image push through the registry gatewaynot today. The registry and git gateways belong to the website and admit what the website configures them for, which is currently people's CLI sessions (wbsp login). A pipeline that builds locally and pushes with a service-account token authenticates the deploy call and then fails at the push with the registry's 401. Admitting a service-account audience at the gateway is a request to the website team, not something a pipeline author can configure. This page will say when that changes.

So the CI pattern that works end to end today is deploy an already-published image: a person publishes from their machine, and the pipeline deploys or re-deploys that image with no build and no push —

export WBSP_API_TOKEN=...
wbsp deploy --image registry.wbsp.ai/<owner>/<repo>/app:<tag> \
  --tenant <tenant> --app <name> --destination aws-prod

(Override hosts with WBSP_REGISTRY_HOST / WBSP_GIT_HOST if your platform uses different domains.)

A service-account token carries the account's rights, not a person's. Its tenant is the one in its issuer, and its verbs are the scopes its administrator granted, re-checked on every call. It is not tied to any individual's project membership, so a staffing change never silently breaks a pipeline — but it also means a demo publish, which follows project access, needs the service account (not a person) to hold that access on the project.

Publish a sandbox, then promote it

wbsp deploy --destination sandbox     # build locally + gateway push + register
wbsp demo promote --sandbox            # or promote from the website

The sandbox destination needs no --backdoor. After publishing, the sandbox is launchable ("Try in sandbox") and promotable to the live demo.

You do not need to declare a sandbox: block: with only a demo: block, a sandbox publish borrows it (and .env.demo), so an app that publishes a demo today publishes a sandbox with no config change. Declare sandbox: only when the sandbox should differ from the demo.

wbsp deploy --destination demo --sandbox is retired. --sandbox remains unchanged on wbsp demo launch/remove/status/promote, which act on an already-published sandbox rather than on a placement.

Why it's safe

  • Collaborators only. The registry gateway authorizes every push against your variant membership: you can push to the container repository of a project you own or collaborate on. Platform operators may push to any variant — recorded in the push audit under its own reason code, so an operator push to someone else's repository is always visible as one.
  • Both gates, before the build. The push rule is narrower than the gate on the deploy or publish itself (which also admits holders of the destination tenant), so wbsp deploy asks both before it builds anything: you'll see Authorized to publish … and Authorized to push ….
  • Demos follow project access, not tenant grants. Publishing a demo needs owner or developer access to the project; launching, inspecting and reaping one is also open to its testers and hosters. You never need an operator to grant you a tenant to work with your own project's demo.
  • No AWS credential ever reaches your machine — the gateway injects the platform ECR credential server-side after authorizing you.
  • Your source is never uploaded — only the built image is pushed.
  • Every push is audited (who pushed which image to which variant).

Want your app to run on home machines? The universal destination

A wbsp deploy to a cloud destination builds the cloud image only (linux/amd64) — if your application is cloud-only, nothing changes for you and you can skip this section.

The universal destination builds linux/arm64 as well and publishes the two as a single multi-architecture index, which is what lets your application run on an Apple Silicon machine — including the wbsp-vm home appliance. It publishes and stops: no deploy, no database, no routes, nothing running, and no tenant involved at any point.

destination:
  universal: {}
wbsp deploy --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
}

Do this if you intend to enable "can run locally" in the catalog: an amd64-only image pulls cleanly on an appliance and then fails with exec format error.

It is also the whole flow for an appliance-only application — one with no cloud destination at all. Publish, then enable "can run locally" in the catalog. It requires the default credential-less route (wbsp login) and is not available with --backdoor.

Declare release: where the appliance will see it. The appliance cannot read your wbsp.yaml; it reads the run manifest stamped on the image, which carries this destination's effective configuration. An empty universal: {} inherits your top-level release:; override it under the block when the appliance needs something different.

Three practical notes:

  • The first universal publish may create a wbsp-multiarch builder. A stock Docker Desktop cannot build multi-platform images, so the CLI provisions a dedicated buildx builder on first use and reuses it afterwards. You do not need to create one, and you should not delete it between publishes.
  • Build time barely moves on an Apple Silicon Mac. The amd64 half was already emulated; the arm64 half added alongside it is the native build. On an amd64 CI runner the reverse holds and arm64 is the emulated half.
  • WBSP_BUILD_PLATFORMS (comma-separated) narrows the published set for a pipeline that has measured the cost. It applies to the universal destination; a cloud deploy is already cloud-only.

Deploying to the cloud as well — order matters

wbsp deploy --destination aws --universal and wbsp deploy --destination aws --universal --publish-only are both retired. Deploying and appliance-publishing are two commands now, and a cloud deploy moves the image tag to a cloud-only manifest:

wbsp deploy --destination aws         # cloud
wbsp deploy --destination universal   # then restore appliance runnability

A cloud deploy of an app that declares universal: prints a reminder to that effect.

What your image tells a deployment target

Your image carries an ai.wbsp.manifest label holding the effective configuration for the destination it was built for — release, port, database.extensions, your component topology, and the names of the secrets you declare. That is how something holding only an image reference knows to run your migrations before starting you.

Secret values never travel: secrets: is published as names and reference keys only, and your database user and password are excluded outright. Nor is any destination other than the one the image was built for.

Your non-secret env: values do travel, in the clear. They are copied into the label verbatim, and reading that label needs no credential beyond the pull — so anyone who may pull your image can read them. That makes env: the wrong home for anything merely sensitive rather than secret: an internal hostname, a bucket or account id, a partner's endpoint. Such values pass the "no secrets in wbsp.yaml" rule and still end up published. If you would rather not hand a value to whoever can pull the image, declare it in secrets:, where only the name is published — see wbsp-yaml-reference.md.

The practical consequence for you: your release: command runs inside your own image, so that image must contain whatever the command needs. If your release runs psql, ship psql.

Troubleshooting

MessageMeaning
403 … authenticated but do not have access to this container repository on a first publishA known gateway bug, not a permissions problem: the repository is created by the very request that is refused. wbsp deploy retries once automatically; a hand-run docker push needs one manual retry.
Multi-platform build is not supported for the docker driverSeen only on the universal destination: the wbsp-multiarch builder could not be provisioned. Check docker buildx version, or set WBSP_BUILD_PLATFORMS=linux/amd64 to publish a cloud-only image (which will not run on an appliance).
--publish-only is retired / --universal is retired / --sandbox is retiredEach is a destination now: --destination universal for the first two, --destination sandbox for the third. The error names the replacement.
no destination "universal" is declaredAdd universal: {} under destination: — declaring the block is how an app opts into appliance publishing.
must not declare type, tenant … on a reserved blockdemo, sandbox and universal have platform-supplied identity; remove those keys. env, secrets and release stay yours.
not allowed to push … (the registry returned 403)You are signed in, but you neither own the variant, collaborate on it, nor hold the operator role. Ask the owner to add you as a collaborator, or sign in as the owning account. wbsp whoami --all shows your account against the repository owner.
the registry rejected your credential (401)Run wbsp login (interactive). If WBSP_API_TOKEN is set: the gateway does not admit a service-account token today — see CI / automation above; deploy a published image with --image instead of pushing from the pipeline.
the registry has no container repository for …The registry path comes from the git remote origin; the catalog knows no variant for that repository.
docker push … failedThe push itself failed; the sentence after the dash says whether it was the credential (401) or entitlement (403).
not logged in — run \wbsp login``No valid session; sign in.
WBSP_REGISTRY_TOKEN is no longer supportedPersonal access tokens were retired — set WBSP_API_TOKEN (CI) or use wbsp login.
could not read the git remote 'origin'The project needs its platform git remote (that's how the registry addresses the repo).
a local container runtime is requiredInstall/start Docker — the build runs locally.

Operators

Nothing to enable — the registry gateway (registry.wbsp.ai, features 043/045) is live. Its validator accepts the audience the website configures it for: since feature 093 that is people's login sessions; the platform-signed CI-token family of feature 075 no longer exists, and a service-account WBSP_API_TOKEN is not admitted at the gateway until the website adds that audience. Personal access tokens are no longer accepted. --backdoor remains available for bootstrap/recovery, and is the path for a self-hosted application that is not a catalog variant (the gateway has no repository for it).