Skip to content

Preview environments

pb preview up creates a full copy of an App for a branch: its own object, its own URL, and its own copy of every database and cache it uses, so a pull request can be reviewed against real data flows without touching production.

Terminal window
pb preview up shop --ref feat-login --wait
# App shop-feat-login created (preview of shop, image registry.example.com/acme/shop:feat-login, ttl 168h0m0s)
# ManagedPostgres shop-feat-login-db created (empty xs)
# Ready: https://shop-feat-login.shop.apps.acme.paasbox.app
pb preview down shop --ref feat-login
# App shop/shop-feat-login deleted; its databases follow (backups stay in the store)
  • Its own App, named <app>-<ref> (the branch name, lower-cased and made into a valid DNS label), scaled to zero by default, at https://<app>-<ref>.<namespace>.<domain>.
  • An image derived from the source app’s, with its tag replaced by the branch — shop:1.4.2 becomes shop:feat-login — or pass --image explicitly, which is what most CI pipelines do.
  • Its own copy of every dependency. Each ManagedPostgres or ManagedValkey the source app uses gets a same-shaped copy, named <preview>-<dep>, owned by the preview App — deleting the preview deletes them too. A database copy is an empty xs instance by default; --from-prod restores it from the source database’s own backups instead, into this new instance, never touching the source. A cache copy is always an empty xs instance — there is nothing in a cache worth copying.
  • A TTL, seven days by default (--ttl), after which a forgotten preview is deleted automatically.

A pull-request pipeline calling pb needs nothing platform-specific — the same commands work from a Forgejo Actions or GitHub Actions job with a kubeconfig for the cluster:

# .github/workflows/preview.yml (illustrative — write it against your own pipeline)
on:
pull_request:
types: [opened, synchronize, closed]
jobs:
preview:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: build and push
if: github.event.action != 'closed'
run: docker build -t registry.example.com/acme/shop:pr-${{ github.event.number }} . && docker push registry.example.com/acme/shop:pr-${{ github.event.number }}
- name: preview up
if: github.event.action != 'closed'
run: pb preview up shop --ref pr-${{ github.event.number }} --image registry.example.com/acme/shop:pr-${{ github.event.number }} --wait
- name: preview down
if: github.event.action == 'closed'
run: pb preview down shop --ref pr-${{ github.event.number }}

Every image change to an App creates a new Knative revision; with spec.traffic unset it takes 100% of the traffic right away. Setting spec.traffic splits it explicitly:

spec:
traffic:
- { revision: latest, percent: 0, tag: green }
- { revision: shop-00042, percent: 100 }

latest always resolves to the newest revision that is actually ready, so a broken image never receives traffic by accident. Editing spec.traffic with kubectl works today; a pb app promote shortcut that shifts the percentage for you is planned.