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

> ## Agent Instructions
> For agents: install the OpenWork desktop app with `brew install --cask openwork` (macOS) or from https://openworklabs.com/download. Do not run `npx openwork`; the npm package named `openwork` is a different project.
> Connect an agent to OpenWork with the MCP Gateway: `claude mcp add --transport http openwork https://api.openworklabs.com/mcp/agent`. Teams sign up at https://app.openworklabs.com?mode=sign-up.
> Step-by-step agent guide: https://openworklabs.com/docs/start-here/use-openwork-from-an-ai-agent.md
> Connect Slack (any organization admin): https://openworklabs.com/docs/slack/connect-slack.md. OpenWork in Slack (@openwork bot) is private alpha for approved organizations. Setup guide: https://openworklabs.com/docs/slack/overview.md. Agent skill: https://openworklabs.com/.well-known/agent-skills/set-up-openwork-slack/SKILL.md.

# Deploy on Google Cloud

> Run OpenWork on GKE Autopilot or Google Cloud Run with Cloud SQL for MySQL.

OpenWork supports two Google Cloud deployment shapes. Use GKE Autopilot and Helm when you need the full chart surface. Use the Cloud Run starter for a serverless Den Web and Den API deployment with a separate migration job.

> [card] [GKE with Helm](https://github.com/different-ai/openwork/blob/dev/docs/gcp-gke-helm.md)
>
> Full chart path, including Kubernetes customization and the optional Gateway.

> [card] [Google Cloud Run](https://github.com/different-ai/openwork/blob/dev/docs/gcp-cloud-run.md)
>
> Serverless starter for Den Web, Den API, Cloud SQL, Secret Manager, and explicit migration jobs.

## Choose a deployment target

Choose GKE when you need Helm hooks, pod-level customization, custom CA mounts, installer artifact volumes, or the optional OpenWork Gateway. Choose Cloud Run when Den Web and Den API fit the request-driven service model and you prefer managed autoscaling without a Kubernetes cluster.

## What Google Cloud manages

* GKE control plane and Autopilot compute lifecycle
* VPC networking, Cloud Load Balancing, firewall rules, and IAM
* Cloud SQL for MySQL, encryption, backups, and failover
* Cloud DNS and Google-managed certificates when you use those services

The OpenWork chart manages Deployments, Services, ConfigMaps, Secret references, probes, the Ingress, and the database migration Job. GKE `BackendConfig` and `ManagedCertificate` resources are applied alongside the chart.

## GKE checklist

1. Enable the Kubernetes Engine, Compute Engine, Cloud SQL Admin, and Service Networking APIs.
2. Create a regional GKE Autopilot cluster and install `gke-gcloud-auth-plugin` if your Cloud SDK does not include it.
3. Configure private services access and create Cloud SQL for MySQL with a private IP.
4. Reserve a global IPv4 address and create the GKE health-check and certificate resources.
5. Store `DATABASE_URL`, `BETTER_AUTH_SECRET`, and `DEN_DB_ENCRYPTION_KEY` in a Kubernetes Secret.
6. Start from the chart's `values.gcp-ingress.yaml` example, install the chart, and verify migrations and readiness.
7. Point both hostnames at the reserved address and wait for the managed certificate to become active.

For exact GKE commands, values, health checks, migration troubleshooting, verification, and cleanup, follow the [GKE operator runbook](https://github.com/different-ai/openwork/blob/dev/docs/gcp-gke-helm.md).

## Cloud Run checklist

1. Enable Cloud Run, Artifact Registry, Cloud SQL Admin, and Secret Manager APIs.
2. Mirror the versioned Den API and Den Web images into Artifact Registry.
3. Create Cloud SQL for MySQL and store independent application secrets in Secret Manager.
4. Render the starter service and job manifests from `packaging/cloud-run`.
5. Execute the migration job before deploying the API and Web revisions.
6. Verify all four health and readiness endpoints before opening setup.

Follow the [Cloud Run operator runbook](https://github.com/different-ai/openwork/blob/dev/docs/gcp-cloud-run.md) for the manifests, exact commands, migration ordering, verification, and upgrade notes.

## Cloud Run with Terraform

### What it deploys

The module deploys Den API and Den web on Cloud Run, Cloud SQL for MySQL 8.4 through the authenticated, verified-TLS Cloud SQL connector, Secret Manager, an Artifact Registry ghcr.io mirror, and one migration job. Apply waits for that job to succeed before updating Den API; application instances do not run migrations. Default endpoints are HTTPS `run.app` URLs. Optional custom domains use a global HTTPS load balancer with a managed certificate.

The common outputs are `web_url`, `api_url`, `database_identifier` (managed Cloud SQL instance ID, or supplied existing Cloud SQL connection name) and `migration_step_identifier` (full Cloud Run job ID). All previous outputs remain available; `database_identifier` is empty for an external non-Cloud-SQL database.

Existing network/subnet, database, service account, certificates and reserved IP inputs remain supported. The applying account needs resource-creation and IAM-setting permissions, including Project IAM Admin, Secret Manager Admin and Cloud Run Admin; Editor alone is insufficient. Alternatively grant runtime IAM separately and use `grant_iam_roles = false`. Use application-default credentials or workload identity.

### Tested scenarios

| Date | Tools | Release | Result |
| - | - | - | - |
| 2026-10-14 | Terraform 1.16.5 | 0.18.58 → dev b8efc5b, digest-pinned | Prior module: setup, fresh browser sign-in, isolated desktop, same-database upgrade, identity/organization persistence and destroy passed with one API instance |
| 2026-10-15 | Terraform 1.16.5 / OpenTofu 1.11.14 | Mocked Google provider | 32 contract tests per tool: write-only fields, protected SQL, measured sizing and two-instance/singleton-job topology passed |
| 2026-10-15 | Terraform 1.16.5; local testkit | 0.18.58 → dev f450b1a, digest-pinned | Two API instances observed in both revisions; one successful migration per deployment (exit 0), setup, fresh browser sign-in, exact admin/org persistence, isolated source Electron, no secret rotation or state values, destroy and empty inventory passed |

The live run used new-deployment write-only mode, db-g1-small and default HTTPS endpoints. Earlier runs exercised Direct VPC egress and the HTTPS load balancer/certificate. Existing databases, legacy write-only adoption, email delivery, concurrent coordinators and production load remain unverified. Missing job CPU/memory or idle-memory samples are not sizing guarantees.

### Support level

> [!WARNING]
>
> OpenWork's Terraform modules are **beta**. If you hit an issue:
>
> * **Customers with a support agreement:** contact your OpenWork team through your usual support channel.
> * **Everyone else:** [open a GitHub issue](https://github.com/different-ai/openwork/issues/new/choose).
> * **Security issues:** report them privately ([SECURITY.md](https://github.com/different-ai/openwork/blob/dev/SECURITY.md)).

The [module](https://github.com/different-ai/openwork/tree/dev/infra/terraform/modules/openwork-gcp-cloud-run)
is available on `dev` in **beta**.

Follow the shared [module contract](https://github.com/different-ai/openwork/blob/dev/infra/terraform/MODULE_CONTRACT.md) and pin to a reviewed commit. Requires Terraform or OpenTofu `>= 1.11, < 2.0`. The module's bounded providers are Google `~> 8.7` and Random `~> 3.9`. Workbot, the runner, Slack and OpenWork Web/gateway are outside this change. A dedicated job orders one deployment; it does not implement a database mutex, dirty marker or coordination across independent deployments.

Defaults: API **1 vCPU / 2 GiB**, web **1 vCPU / 1 GiB**, one warm instance per service, maximum three; Cloud SQL **db-g1-small (1.7 GB)** with 20 GB disk and backups. The old micro tier exhausted its reported memory quota at idle. Database, services and job have deletion protection by default; explicitly turn it off only for disposable stacks. The deprecated nested `database.deletion_protection` override retains precedence.

### Upgrade notes

**Database disk default: 10 → 20 GB.** Existing direct users who omitted
`database.disk_size_gb` will see an in-place increase in their next plan.
[Cloud SQL storage increases are online, without downtime](https://docs.cloud.google.com/sql/docs/mysql/instance-settings#storage-capacity):
this storage-only change does not restart or replace the instance, but **cannot
be reversed by shrinking the disk**. Review the entire plan for unrelated changes.
If automatic growth has already taken the disk above 20 GB, preserve its current
allocation rather than attempting a reduction.

Estimated zonal MySQL SSD cost in **us-central1** rises from \*\*$1.70 to
$3.40/month (+$1.70)** at the public list price of $0.17/GB-month, checked
2026-10-19. Database compute, backups, networking, taxes and discounts are
excluded; see [Cloud SQL pricing](https://cloud.google.com/sql/pricing/).
To keep an existing 10 GB disk, set this **before applying the increase**,
merging it into your existing `database` object without dropping other settings:

```hcl theme={null}
database = { disk_size_gb = 10 }
```

Automatic growth remains on; this does not cap growth or undo an already applied
increase. Instance tier and API/web defaults are unchanged. Managed installer
sizes are separately pinned and untouched. See the
[Small / trial table](https://openworklabs.com/docs/self-host/deploy-to-your-cloud/overview.md#small--trial-sizing)
for the baseline and its trial-only shared-core database exception.

Back up and inventory every schema-changing owner, including other installations and manual jobs sharing the database. Freeze retries/rollouts and select one coordinator. When upgrading from per-replica migration definitions, enter maintenance: scale old Den API to zero or stop it, disable autoscale/replacement/retry sources, wait for old containers/jobs and DB sessions/in-flight DDL to terminate, and fence old migration credentials. Drain other incompatible consumers too. If that barrier cannot be established, do not migrate.

Update both pinned image digests, apply the single native job, verify its successful completion, then roll out API-only instances. A failed or unknown job must not promote a revision or trigger a blind retry. Resume scaling only after data and sign-in verification. Never roll back to old migration-capable definitions or automatically reverse the database schema. This module still shares one database principal between app and migration; separate DDL/DML principals remain a follow-up.

Write-only secrets are now the default: ephemeral generation feeds provider write-only arguments and values persist in Secret Manager rather than state. The earlier module merged first with legacy state-backed secrets; this contract revision changes that default. If you deployed the earlier mode, explicitly keep `write_only_secrets = false` until you have preserved all exact current values through the ephemeral `preserved_secrets_wo` map; never silently regenerate an auth or encryption key. Read the README's adoption and partial-failure cautions first. The legacy bootstrap output is null in write-only mode; retrieve the setup code directly from Secret Manager without logging it. Digest pinning remains the only image default; the explicit mutable-tag opt-in is not recommended. Existing external database URLs must use the authenticated connector socket or verified `sslaccept=strict` TLS; unverified URLs are rejected.

#### Secrets and later runs

* **Later applies and upgrades do not rotate secrets or sign anyone out.** Values are written once, tracked by a version number. Unchanged version numbers keep the stored values unchanged, even when a later evaluation creates temporary candidate values. The 2026-10-15 live release-to-dev run already passed in this exact write-only mode with unchanged version numbers. The default change and version-input wiring were checked offline, not reapplied live.
* **Rotation is deliberate:** increase the relevant `*_version` input (`secret_data_wo_version` per secret name, or `database_password_wo_version` for SQL). A change that forces replacement of a secret version resource can also regenerate its value. Never decrease or reset counters: any counter change can trigger a write. Coordinate SQL password rotation with its `DATABASE_URL` secret in the same apply; do not rotate the URL independently with a new password that SQL did not adopt.
* Rotating the auth secret **signs everyone out**. Rotating the DB encryption key makes stored credentials unreadable: **never rotate `DEN_DB_ENCRYPTION_KEY` without a re-encryption procedure.** This module does not perform re-encryption.
* **Secret Manager is now the only managed copy of generated values.** Protect it from deletion and unauthorized access, and include its values in backup and disaster recovery. Database backups do not include the separate encryption key.
* Terraform and OpenTofu **no longer detect manual edits** to these values: write-only fields are not stored for comparison, so an unchanged version number will not repair an out-of-band value change.
* **Losing state or recreating secret resources can regenerate values.** Restore state/secrets or carefully import existing resources before applying. Recreating keys is not a recovery procedure for a populated installation.
* Both tools require **version 1.11 or later**, below 2.0 for this module.

### State guidance

Use encrypted remote state, restricted access and locking (for example GCS). Write-only mode keeps generated values out of state by default. The explicit, not-recommended legacy opt-out retains values in state; historical snapshots still require protection after write-only adoption. Never commit state, plan files, credentials or secret variable files. Root configurations own provider/backend blocks and lockfiles; the reusable module ships no registry-specific lock. The complete example ships a Terraform lock: when switching to OpenTofu, regenerate the lock in your own copied root with `tofu init`, then verify `tofu init -lockfile=readonly`. Each registry has different archives/checksums. For S3 state use native `use_lockfile = true`, not DynamoDB locking.

### Cost notes

Budget for warm Cloud Run instances, db-g1-small, disk/backups, secrets and image cache; custom domains add load balancer and IP charges. This measured smoke baseline is not production or HA sizing, and the old micro-based price estimate no longer applies. Min instances zero reduces idle cost at the expense of sign-in cold starts. Direct VPC egress may hold a subnet busy for roughly two hours after deleting services: allow teardown time. Email is optional for setup/sign-in; invites and reset require SMTP or Resend.

See the [module README](https://github.com/different-ai/openwork/tree/dev/infra/terraform/modules/openwork-gcp-cloud-run) for all inputs, outputs, defaults, write-only adoption, tool-specific lock regeneration and the disposable complete example.

Next: [Create the first administrator](https://openworklabs.com/docs/self-host/deploy-to-your-cloud/first-administrator.md).


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