Operations

This chapter covers how the portal is deployed and how its Postgres schema is managed.

Deploy the portal

The portal is distributed as a single Docker image that contains the Rust binary and the built frontend assets. The image entrypoint is /usr/local/bin/portal.

Required environment variables

See .env.example in the repository root. In production the following must be set:
  • APP_BASE_URL – the public URL customers use in their browser.
  • DATABASE_URL – Postgres connection string.
  • POCKET_ID_ISSUER, POCKET_ID_CLIENT_ID, POCKET_ID_CLIENT_SECRET – OIDC configuration.
  • R2_ACCOUNT_ID, R2_ACCESS_KEY_ID, R2_SECRET_ACCESS_KEY, R2_BUCKET_NAME – object storage.
  • STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET, STRIPE_PRICE_ID_* – billing integration.
Set DEV_MODE=true only for local development; in that mode OIDC, R2, and Stripe are stubbed. In production the portal refuses to start unless STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET, and STRIPE_PRICE_ID_PRO are non-empty.

Database provisioning

The portal applies its full schema automatically at startup. The schema is embedded in the binary as portal/db/schema.sql and is applied idempotently using CREATE ... IF NOT EXISTS statements. This keeps deployment simple: any empty Postgres database pointed at by DATABASE_URL will be provisioned on first startup. This approach trades granular rollback for operational simplicity. Because the schema is applied as a single script, downgrades are handled by deploying an older container image with an older schema file, not by reversing individual migrations.

Deploy order

  1. Build and push the new portal image.
  2. Roll out the new portal pods.
  3. The pods provision or update the schema automatically on startup.
  4. Run smoke tests: /health, log in through OIDC, upload a small artifact.

Rolling back a schema change

To revert a bad schema change, redeploy the previous portal image. The older embedded schema will be applied idempotently on startup. Note that this does not delete columns or tables added by the newer schema; it only ensures the older schema’s expected objects exist. Data in columns or tables that the older binary does not reference is left untouched in Postgres. For destructive rollbacks, restore from a database backup taken before the deploy.

Local development

Start an empty Postgres instance and run the portal with DEV_MODE=true. The embedded schema will be applied automatically on first startup.