Skip to main content
Google Cloud deployment is opt-in. OAO does not include a deployment workflow, target project, container repository, workload identity, or Cloud Run service configuration. Create and operate those resources with your own infrastructure tooling and delivery pipeline.

Topology

A small production topology has:
  • a public Cloud Run service that serves the compiled console and API
  • a Cloud Run worker pool for the continuous runtime worker
  • a managed PostgreSQL instance shared by both application processes
Deploy the same immutable image to both application workloads. The public service runs the image’s api entrypoint and the worker pool runs its worker entrypoint. Both workloads need the same DATABASE_URL and OAO_CREDENTIAL_ENCRYPTION_KEY. Do not give the worker a public endpoint. The worker delivers event webhooks, so give it outbound HTTPS access to their endpoints.

Build and deliver an image

Create an Artifact Registry Docker repository in your own project and authenticate the Google Cloud CLI as an identity with permission to push to that repository. Configure Docker authentication before building and publishing. Replace each placeholder with your own value:
VITE_OAO_API_MODE is a console build setting; changing it on a running container does not rebuild the console. See the Google Cloud Docker authentication guide for credential-helper setup. Resolve the published tag to an immutable digest:
The command returns sha256:<IMAGE_DIGEST>. Use that digest in the deployment image reference:
Configure your deployment tool to update the API service and worker pool with that digest. Terraform is one option, but it is not required. Keep service names, regions, identities, registry access, scaling, health checks, and secrets in the infrastructure and delivery system you own. Repository CI remains portable: it runs pnpm check, pnpm test:postgres:fresh, and a local Docker build without publishing or deploying an image. Application versioning is also separate from infrastructure delivery; see Releases.

Runtime configuration

Store production secrets in a secret manager and grant each workload access only to the values it needs. Configure these shared settings on both the API and worker; all example values are placeholders:
Configure these additional settings only on the API service:
Use the container image’s default api command for the public service and the worker command for the worker pool. Migrations run idempotently when each process starts, using DATABASE_URL. Its database principal must own the OAO database and have CREATEROLE, permission to create pgcrypto and schema objects, and permission to manage role memberships, object grants, and function ownership. An ordinary application-only database login cannot run these startup migrations. The bundled migrations support a non-superuser database owner on Cloud SQL. They create restricted no-login roles, including oao_app, which application transactions assume after migration. Do not grant PostgreSQL superuser or BYPASSRLS solely for OAO; verify the required ownership and role-management permissions with your PostgreSQL provider. For a full explanation of local configuration and disposable PostgreSQL checks, see Local development. The Railway guide has the same application process layout on another managed platform.

Authentication

Choose one hosted authentication provider and configure its settings only on the API service. The worker does not need AUTH_PROVIDER, WorkOS credentials, or IAP audience and identity settings. For WorkOS, set AUTH_PROVIDER=workos and provide the WorkOS configuration and callback URL for https://<PUBLIC_HOSTNAME>/v1/auth/callback. Provision an existing OAO principal and membership before the first hosted login. For Google IAP, set AUTH_PROVIDER=iap and use an exact, target-specific audience:
IAP controls who reaches the service; OAO verifies the signed assertion again and ignores unsigned identity headers. Keep the worker private and do not use service-account keys. The Authentication reference documents organization roles, project access, and service-to-service credentials.

IAP first-login onboarding

When the API starts with AUTH_PROVIDER=iap against an empty database, it creates Default organization and Default project with generated IDs; PostgreSQL stores the default selection and a one-time ownership marker. Other authentication providers never create these defaults. No setup screen, bootstrap command, or tenant environment variable is needed. The first verified human login becomes the organization owner. Later verified humans join the default project as members with explicit developer scopes and no wildcard, tenant-administration, audit, MCP-configuration, or credential-write permissions. IAP is the admission allowlist, so restrict it to trusted people before the first login. Service accounts use scoped API keys and cannot claim ownership. Membership, identity, ownership claim, and audit entry commit together, so concurrent first logins create exactly one owner. Removing an owner or restarting never reopens the claim. New projects initially include only their creator. An Owner or Admin can use the project’s Members page to add an existing IAP user by their verified email after that user has signed in once. Remove from project keeps the user’s organization access, while Remove organization access is the organization-wide offboarding action. The default project’s membership cannot be removed, and the default project cannot be deleted, because it anchors IAP console authentication. The default image discovers the authentication provider from the API at runtime; an unset or empty build-time AUTH_PROVIDER does not select development login. A console bundle built for a different provider cannot be repaired by changing the runtime environment: rebuild and release the image. The IAP migrations support a non-superuser database owner with CREATEROLE. They temporarily grant schema CREATE to the oao_auth function-owner role during ownership transfer and revoke it in the same migration transaction.

Upgrading from IAP tenant variables

Earlier releases required IAP_ORGANIZATION_ID and IAP_PROJECT_ID. Both are now ignored. An existing installation reuses its single IAP tenant link for the configured audience and keeps its IDs, names, users, and roles. Deploy the new release while the old variables are still set, verify that existing owners, members, and API-key callers resolve to the same tenant, then remove the variables. Rolling back to an older release requires restoring them; keep the database defaults and ownership marker. If startup reports that operator review is required, the installation has a missing or ambiguous tenant link, or an ownerless tenant. Use a reviewed migration credential to inspect oao.iap_default_tenant, oao.auth_tenant_links, and the organization and project memberships. Match the tenant link to the exact IAP audience and restore an authorized owner’s membership; never reset the ownership marker to let an arbitrary login become owner. The provision:iap command can still link a reviewed existing principal to its IAP identity. It does not grant membership, replace a bound identity, or override a conflicting email.

Verify

After deployment, confirm that:
  1. The public service returns 200 from /readyz and serves the console.
  2. The worker pool can access PostgreSQL and reaches its private /readyz check.
  3. The selected authentication flow returns to the public service.
  4. Both workloads run the intended immutable image digest.