Skip to main content
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.

GKE with Helm

Full chart path, including Kubernetes customization and the optional Gateway.

Google Cloud Run

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.

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 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

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

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.
  • Security issues: report them privately (SECURITY.md).
The module is available on dev in beta. Follow the shared module contract 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: 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.70to1.70 to 3.40/month (+1.70)∗∗atthepubliclistpriceof1.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. To keep an existing 10 GB disk, set this before applying the increase, merging it into your existing database object without dropping other settings:
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 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 for all inputs, outputs, defaults, write-only adoption, tool-specific lock regeneration and the disposable complete example. Next: Create the first administrator.