Deploying Cloud SQL for PostgreSQL on GCP

Workload Identity Federation and GitHub Actions
Once a VM on Google Cloud is successfully deployed,
We need to automate the process with GitHub Actions by storing a service account key in GitHub Secrets.
This time, explore automation with GCP WIF:
No service account keys
Instead, we’ll use Workload Identity Federation (WIF) and the official
google-github-actions/auth@v1
action to securely deploy Cloud SQL PostgreSQL with Terraform.
Why Cloud SQL for PostgreSQL?
Cloud SQL is Google Cloud’s fully managed database service. Instead of installing PostgreSQL manually on VMs (which means dealing with backups, HA, scaling, monitoring, etc.), Cloud SQL handles all of that for you.
Key benefits include:
Built-in high availability and failover
Scalability (both vertical and horizontal)
Strong security (IAM integration, encryption, private networking)
Easy integration with other GCP services (GKE, Compute Engine, BigQuery, Dataflow, etc.)
Automated backups and point-in-time recovery
Step 1: Terraform Setup for Cloud SQL
We’ll create a file sql.tf
with the required resources.
Private IP address for Cloud SQL
resource "google_compute_global_address" "private_ip_address" {
provider = google-beta
name = "private-ip"
purpose = "VPC_PEERING"
address_type = "INTERNAL"
prefix_length = 16
network = google_compute_network.main_vpc.id
project = var.project_id
}
VPC Connector (private connection between Cloud SQL and VPC)
resource "google_service_networking_connection" "private_vpc_connection" {
provider = google-beta
network = google_compute_network.main_vpc.id
service = "servicenetworking.googleapis.com"
reserved_peering_ranges = [google_compute_global_address.private_ip_address.name]
}
Cloud SQL PostgreSQL Instance
resource "google_sql_database_instance" "postgres_instance" {
name = var.db_name
project = var.project_id
region = var.region
database_version = "POSTGRES_15"
deletion_protection = false
settings {
tier = var.db_machine_type
disk_size = 100
disk_type = "PD_SSD"
disk_autoresize = true
backup_configuration {
enabled = true
start_time = "02:00"
point_in_time_recovery_enabled = true
}
ip_configuration {
ipv4_enabled = false
private_network = google_compute_network.main_vpc.id
require_ssl = true
}
}
depends_on = [google_service_networking_connection.private_vpc_connection]
}
Database user and app DB
resource "random_password" "postgres_password" {
length = 16
special = true
}
resource "google_sql_user" "postgres_user" {
name = "postgres"
instance = google_sql_database_instance.postgres_instance.name
password = random_password.postgres_password.result
}
resource "google_sql_database" "app_database" {
name = "appdb"
instance = google_sql_database_instance.postgres_instance.name
}
Step 2: Workload Identity Federation (WIF)
Normally, you’d store service account keys in GitHub secrets.
With WIF, we skip that and instead let GitHub authenticate to GCP using temperory tokens via OIDC.
We need 3 components:
Workload Identity Pool
Workload Identity Provider
Service Account with permissions
Service Account
resource "google_service_account" "github_actions_sa" {
project = var.project_id
account_id = "github-actions-sa"
display_name = "GitHub Actions Service Account"
}
Grant required roles:
resource "google_project_iam_member" "github_actions_permissions" {
for_each = toset([
"roles/compute.admin",
"roles/storage.admin",
"roles/cloudsql.admin",
"roles/servicenetworking.networksAdmin",
"roles/iam.serviceAccountUser"
])
project = var.project_id
role = each.value
member = "serviceAccount:${google_service_account.github_actions_sa.email}"
}
Workload Identity Pool and Provider
module "gh_oidc" {
source = "terraform-google-modules/github-actions-runners/google//modules/gh-oidc"
version = "~> 3.1"
project_id = var.project_id
pool_id = var.workload_identity_pool_name
provider_id = var.workload_identity_provider_name
sa_mapping = {
(google_service_account.github_actions_sa.account_id) = {
sa_name = google_service_account.github_actions_sa.name
attribute = "attribute.repository/your-github-username/your-repo-name"
}
}
}
Step 3: GitHub Actions Workflow
Here’s a simplified workflow (.github/workflows/deploy.yml
):
name: Terraform Deployment with PostgreSQL
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
terraform:
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
pull-requests: write
steps:
- uses: actions/checkout@v4
- id: auth
uses: google-github-actions/auth@v1
with:
create_credentials_file: true
workload_identity_provider: ${{ env.WIF_PROVIDER }}
service_account: ${{ env.WIF_SERVICE_ACCOUNT }}
- uses: google-github-actions/setup-gcloud@v1
- uses: hashicorp/setup-terraform@v3
with:
terraform_version: 1.6.0
- run: terraform init
- run: terraform validate
- run: terraform plan -out=tfplan
- if: github.ref == 'refs/heads/main'
run: terraform apply -auto-approve tfplan
Step 4: Outputs
Add these to outputs.tf
:
output "db_instance_name" {
value = google_sql_database_instance.postgres_instance.name
}
output "db_connection_name" {
value = google_sql_database_instance.postgres_instance.connection_name
}
output "db_private_ip" {
value = google_sql_database_instance.postgres_instance.private_ip_address
}
output "db_password" {
value = random_password.postgres_password.result
sensitive = true
}
Step 5: Testing the Deployment
Deploy with:
git add .
git commit -m "Add PostgreSQL deployment with WIF"
git push origin main
Check database:
gcloud sql instances list
gcloud sql connect postgres-db --user=postgres
Why WIF is Better
Security perks:
No long-lived keys in GitHub
Tokens auto-rotate
Full audit logs
Operational perks:
Zero key management overhead
Works natively with GitHub OIDC
Scales easily across repos
What We Achieved
✅ Deployed Cloud SQL PostgreSQL with private networking
✅ Secured access using VPC peering
✅ Used Workload Identity Federation (keyless authentication)
✅ Automated everything with GitHub Actions
✅ Added monitoring, backup, and security configs
👉 This gives you a secure, automated, and production-ready PostgreSQL setup on GCP without managing credentials manually.
Deploying Cloud SQL for PostgreSQL on GCP with Workload Identity Federation and GitHub Actions
(... existing rewritten article ...)
Understanding Workload Identity Federation (WIF)
What is WIF?
Workload Identity Federation is a Google Cloud feature that lets external systems (like GitHub Actions, AWS, or Azure) securely access GCP resources without needing service account keys.
Instead of storing a JSON key file (which can be leaked or stolen), GitHub issues a short-lived OIDC token, and Google Cloud uses it to verify identity and grant temporary access.
Think of it like:
- GitHub vouches for your workflow run → GCP trusts it → GCP gives a time-limited access token.
Why is it better than service account keys?
🔒 No secrets in GitHub → nothing to accidentally leak
⏳ Short-lived tokens → automatically expire after use
🔁 No manual key rotation → GCP handles it
📜 Full audit logs → every authentication event is traceable
🌍 Scales across repos/teams → one pool can serve many repos
Creating Workload Identity Federation (Step-by-Step)
You can set it up either with Terraform (recommended for infra as code) or gcloud CLI.
Here’s the high-level process:
1. Create a Workload Identity Pool
This is like a container where you group external identities.
gcloud iam workload-identity-pools create "github-pool" \
--project="YOUR_PROJECT_ID" \
--location="global" \
--display-name="GitHub Actions Pool"
2. Create a Workload Identity Provider
This connects the pool to GitHub’s OIDC tokens.
gcloud iam workload-identity-pools providers create-oidc "github-provider" \
--project="YOUR_PROJECT_ID" \
--location="global" \
--workload-identity-pool="github-pool" \
--display-name="GitHub OIDC Provider" \
--attribute-mapping="google.subject=assertion.sub,attribute.repository=assertion.repository" \
--issuer-uri="https://token.actions.githubusercontent.com"
Here,
google.subject=assertion.sub
ensures each workflow run is unique.attribute.repository=assertion.repository
restricts access to a specific GitHub repo.
3. Create a Service Account for GitHub
This is the account that Terraform will use inside GCP.
gcloud iam service-accounts create github-actions-sa \
--project="YOUR_PROJECT_ID" \
--display-name="GitHub Actions Service Account"
Grant roles to this account (for Cloud SQL, networking, storage, etc.):
gcloud projects add-iam-policy-binding YOUR_PROJECT_ID \
--member="serviceAccount:github-actions-sa@YOUR_PROJECT_ID.iam.gserviceaccount.com" \
--role="roles/cloudsql.admin"
(Repeat for other roles like /compute.admin
, /storage.admin
etc.)
4. Allow GitHub Workflows to Impersonate the Service Account
gcloud iam service-accounts add-iam-policy-binding \
github-actions-sa@YOUR_PROJECT_ID.iam.gserviceaccount.com \
--project="YOUR_PROJECT_ID" \
--role="roles/iam.workloadIdentityUser" \
--member="principalSet://iam.googleapis.com/projects/YOUR_PROJECT_NUMBER/locations/global/workloadIdentityPools/github-pool/attribute.repository/YOUR_GITHUB_USERNAME/YOUR_REPO"
This ensures only your GitHub repo can impersonate this service account.
Benefits Recap
By using Workload Identity Federation:
No long-term secrets → safer workflows
Automatic key rotation → no maintenance burden
Granular access → restrict per repo or per branch
Better compliance → audit logs for every request
Easier scaling → one setup works for multiple repos/teams
✅ With WIF in place, your GitHub Actions pipeline can securely deploy resources (like Cloud SQL for PostgreSQL) to GCP without ever touching a static key.
Workload Identity Federation Terraform vs gcloud CLI Setup
You can set up WIF either using Terraform (recommended for production, repeatability, and automation) or gcloud CLI (good for quick testing or one-off setups).
Here’s how both approaches compare step by step:
1. Create a Workload Identity Pool
Terraform
module "gh_oidc" {
source = "terraform-google-modules/github-actions-runners/google//modules/gh-oidc"
version = "~> 3.1"
project_id = var.project_id
pool_id = var.workload_identity_pool_name
pool_display_name = var.workload_identity_pool_display_name
provider_id = var.workload_identity_provider_name
sa_mapping = {
(google_service_account.github_actions_sa.account_id) = {
sa_name = google_service_account.github_actions_sa.name
attribute = "attribute.repository/your-github-username/your-repo-name"
}
}
}
Google cloud CLI
gcloud iam workload-identity-pools create "github-pool" \
--project="YOUR_PROJECT_ID" \
--location="global" \
--display-name="GitHub Actions Pool"
2. Create a Workload Identity Provider
Terraform (inside the module above)
Terraform automatically creates the OIDC provider when you set provider_id
and map attributes.
Gcloud CLI
gcloud iam workload-identity-pools providers create-oidc "github-provider" \
--project="YOUR_PROJECT_ID" \
--location="global" \
--workload-identity-pool="github-pool" \
--display-name="GitHub OIDC Provider" \
--attribute-mapping="google.subject=assertion.sub,attribute.repository=assertion.repository" \
--issuer-uri="https://token.actions.githubusercontent.com"
3. Create a Service Account
Terraform
resource "google_service_account" "github_actions_sa" {
project = var.project_id
account_id = "github-actions-sa"
display_name = "GitHub Actions Service Account"
}
gcloud CLI
gcloud iam service-accounts create github-actions-sa \
--project="YOUR_PROJECT_ID" \
--display-name="GitHub Actions Service Account"
4. Grant Roles to the Service Account
Terraform
resource "google_project_iam_member" "github_actions_permissions" {
for_each = toset([
"roles/compute.admin",
"roles/storage.admin",
"roles/cloudsql.admin",
"roles/servicenetworking.networksAdmin",
"roles/iam.serviceAccountUser"
])
project = var.project_id
role = each.value
member = "serviceAccount:${google_service_account.github_actions_sa.email}"
}
Gcloud CLI
gcloud projects add-iam-policy-binding YOUR_PROJECT_ID \
--member="serviceAccount:github-actions-sa@YOUR_PROJECT_ID.iam.gserviceaccount.com" \
--role="roles/cloudsql.admin"
(Repeat for other roles like compute.admin
, storage.admin
, etc.)
5. Allow GitHub Repo to Impersonate the Service Account
Terraform (via module gh_oidc
)
The sa_mapping
block inside the module ensures only a specific GitHub repo can impersonate the service account.
gcloud CLI
gcloud iam service-accounts add-iam-policy-binding \
github-actions-sa@YOUR_PROJECT_ID.iam.gserviceaccount.com \
--project="YOUR_PROJECT_ID" \
--role="roles/iam.workloadIdentityUser" \
--member="principalSet://iam.googleapis.com/projects/YOUR_PROJECT_NUMBER/locations/global/workloadIdentityPools/github-pool/attribute.repository/YOUR_GITHUB_USERNAME/YOUR_REPO"
✅ With either method, you end up with:
A Workload Identity Pool & Provider tied to GitHub OIDC
A Service Account with limited roles
Secure keyless authentication for GitHub Actions
Which One Should You Use?
Terraform → Best for teams, automation, repeatable infra setups, and version control.
gcloud CLI → Quick setup, testing, or one-time experiments.
👉 Ideally, use Terraform in production and CLI for quick validation or prototyping.
Subscribe to my newsletter
Read articles from Pratiksha kadam directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
