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 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 omitteddatabase.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 **3.40/month (+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:
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
*_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.