> ## Documentation Index
> Fetch the complete documentation index at: https://docs.oao.sh/llms.txt
> Use this file to discover all available pages before exploring further.

# Deploy to Google Cloud

> Run OAO on Google Cloud with an opt-in Cloud Run and PostgreSQL topology.

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](/integrations/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:

```sh theme={null}
gcloud auth configure-docker "<REGION>-docker.pkg.dev"
docker build --build-arg VITE_OAO_API_MODE=http --tag "<REGION>-docker.pkg.dev/<PROJECT_ID>/<ARTIFACT_REPOSITORY>/oao:<IMAGE_TAG>" .
docker push "<REGION>-docker.pkg.dev/<PROJECT_ID>/<ARTIFACT_REPOSITORY>/oao:<IMAGE_TAG>"
```

`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](https://cloud.google.com/artifact-registry/docs/docker/authentication)
for credential-helper setup.

Resolve the published tag to an immutable digest:

```sh theme={null}
gcloud artifacts docker images describe "<REGION>-docker.pkg.dev/<PROJECT_ID>/<ARTIFACT_REPOSITORY>/oao:<IMAGE_TAG>" --format="value(image_summary.digest)"
```

The command returns `sha256:<IMAGE_DIGEST>`. Use that digest in the deployment
image reference:

```text theme={null}
<REGION>-docker.pkg.dev/<PROJECT_ID>/<ARTIFACT_REPOSITORY>/oao@sha256:<IMAGE_DIGEST>
```

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](/reference/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:

```dotenv theme={null}
NODE_ENV=production
DATABASE_URL=postgresql://<DATABASE_USER>:<URL_ENCODED_PASSWORD>@<DATABASE_HOST>:5432/<DATABASE_NAME>
OAO_CREDENTIAL_ENCRYPTION_KEY=<BASE64_ENCODED_32_BYTE_SECRET>
```

Configure these additional settings only on the API service:

```dotenv theme={null}
API_KEY_PEPPER=<RANDOM_SECRET>
APP_ORIGIN=https://<PUBLIC_HOSTNAME>
OAO_SERVE_CONSOLE=true
```

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](/getting-started/local-development). The
[Railway guide](/getting-started/railway) 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:

```dotenv theme={null}
IAP_EXPECTED_AUDIENCE=/projects/<PROJECT_NUMBER>/locations/<REGION>/services/<CLOUD_RUN_SERVICE>
```

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](/reference/authentication)
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.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.