Skip to content

Cloud SQL for Postgres

Runway supports provisioning and connecting to managed Cloud SQL for Postgres instances from your GKE workloads. Unlike unmanaged instances, Runway owns the lifecycle of the database — you do not need to enable PSC or obtain PSC details.

File an MR to add your database to config/runtimes/gke/cloud-sql/managed.yml in the Runway provisioner:

config/runtimes/gke/cloud-sql/managed.yml
- name: my-service-db
provider: GCP
db_version: "POSTGRES_18"
region: us-east1
zone: us-east1-b
env_configuration:
staging:
instance_type: 2xsmall
disk_size_gb: 10
backup_configuration:
transaction_log_retention_days: 2
retained_backups_count: 3
production:
instance_type: xsmall
disk_size_gb: 10
backup_configuration:
transaction_log_retention_days: 7
retained_backups_count: 7
labels:
owner_email_handle: my-team
department: eng-infra
department_group: eng-infra-my-group
product_category: my-category

The name you choose here is your database name and is referenced in the steps below.

The application credentials (username and password) created for the app should be stored in Vault by the app owner and exposed to the workload as environment variables via workloadSecrets in gke-service.yaml — choose whatever variable names make sense for your application. See secrets management for details.

In config/runtimes/gke/workloads.yml, add a postgres_instances key to your workload entry with the database name from the previous step:

config/runtimes/gke/workloads.yml
your-service-gke:
postgres_instances:
- my-service-db

This grants the GKE workload’s service account the IAM permissions required to connect to Cloud SQL.

Add cloudsql_instances to your gke-service.yaml. For managed instances, the project is always gitlab-runway-<env> (for example, gitlab-runway-staging or gitlab-runway-production), the region is whatever region you specified in managed.yml, and the instance name is your database name:

gke-service.yaml
apiVersion: runway/v1
kind: RunwayKubernetesDeployment
your-service-gke:
serviceMixins:
- "runway-gke.service-mixin"
cloudsql_instances:
- instance_connection_name: gitlab-runway-<env>:<region>:<name>

For example:

cloudsql_instances:
- instance_connection_name: gitlab-runway-staging:us-east1:my-service-db

Connecting to a managed Cloud SQL instance works the same as for unmanaged instances. The Cloud SQL Auth Proxy exposes databases on localhost with sequential ports starting at 5432:

InstancePort
First5432
Second5433
Third5434

Connect using:

  • Host: 127.0.0.1
  • Port: 5432 (first instance), 5433 (second), etc.
  • Username and password via environment variables (see secrets management)

The provisioner creates a GitLab project on ops.gitlab.net for every Postgres instance. Each project has a scheduled pipeline that restores the latest backup to a separate Cloud SQL instance and, optionally, runs a data validation query against the restored database.

For production Cloud SQL instances, an alert will be fired if a restore validation pipeline was not triggered in the last 24 hours or if a pipeline has started but not completed within 2 hours.

The data validation query is optional and is configured in the env_configuration.<env>.backup_configuration.restore_validation field:

  • Omitted entirely - the scheduled pipeline still runs and restores the latest backup, but no data validation query is executed.
  • Set with database, user, vault_secret_name, vault_secret_key, and query - the restore test runs and the validation query is executed against the restored database.

Unlike the Cloud Run runtime, managed Cloud SQL on GKE does not auto-create the validation user. You must supply credentials for a user that exists in the source database with privileges to run the validation query. The password is read from a Vault KV v2 secret, where:

  • The secret name (path) is env/<environment>/service/<runway_service_id>/<vault_secret_name>.
  • <vault_secret_key> is the key inside that secret’s data whose value holds the password.

runway_service_id is auto-derived from the workload that owns this Cloud SQL instance - you only need to supply vault_secret_name (the last path segment) and vault_secret_key (the key within the secret).

When a validation query is provided, it should return 1 row of at least 2 columns to pass validation. The two columns can be data of any kind and will be printed in the CI job. Users can generate summary statistics and expose them through the two columns (see example below). The query should return no rows if the data is invalid. An alert will be fired if the validation query fails on production.

config/runtimes/gke/cloud-sql/managed.yml
- name: runway-db-foo
...
env_configuration:
staging:
backup_configuration:
restore_validation:
database: runway
user: validator
vault_secret_name: cloudsql-restore-validation
vault_secret_key: password
query: "SELECT COUNT(*) AS row_count, MAX(timestamp_column) AS most_recent_timestamp FROM your_table WHERE timestamp_column >= NOW() - INTERVAL '1 DAY' LIMIT 1;"