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
BackendConfig and ManagedCertificate resources are applied alongside the chart.
GKE checklist
- Enable the Kubernetes Engine, Compute Engine, Cloud SQL Admin, and Service Networking APIs.
- Create a regional GKE Autopilot cluster and install
gke-gcloud-auth-pluginif your Cloud SDK does not include it. - Configure private services access and create Cloud SQL for MySQL with a private IP.
- Reserve a global IPv4 address and create the GKE health-check and certificate resources.
- Store
DATABASE_URL,BETTER_AUTH_SECRET, andDEN_DB_ENCRYPTION_KEYin a Kubernetes Secret. - Start from the chart’s
values.gcp-ingress.yamlexample, install the chart, and verify migrations and readiness. - Point both hostnames at the reserved address and wait for the managed certificate to become active.
Cloud Run checklist
- Enable Cloud Run, Artifact Registry, Cloud SQL Admin, and Secret Manager APIs.
- Mirror the versioned Den API and Den Web images into Artifact Registry.
- Create Cloud SQL for MySQL and store independent application secrets in Secret Manager.
- Render the starter service and job manifests from
packaging/cloud-run. - Execute the migration job before deploying the API and Web revisions.
- Verify all four health and readiness endpoints before opening setup.
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 HTTPSrun.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
The module is available ondev 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 10 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
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 keepwrite_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
*_versioninput (secret_data_wo_versionper secret name, ordatabase_password_wo_versionfor 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 itsDATABASE_URLsecret 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_KEYwithout 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 withtofu init, then verify tofu init -lockfile=readonly. Each registry has different archives/checksums. For S3 state use native use_lockfile = true, not DynamoDB locking.