Deploying Cloud SQL for PostgreSQL on GCP

Pratiksha kadamPratiksha kadam
8 min read

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:

  1. Workload Identity Pool

  2. Workload Identity Provider

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


0
Subscribe to my newsletter

Read articles from Pratiksha kadam directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Pratiksha kadam
Pratiksha kadam