Zero to Production: GitHub Actions CI/CD into GKE with Workload Identity

2025-03-05

Introduction

What Are We Building? 🚀

We're building a secure GitHub Actions pipeline that deploys to GKE without storing any credentials in GitHub. Instead of using service account keys, we'll use Workload Identity Federation (OIDC) so GitHub can impersonate a GCP Service Account.

No long-lived keys. No kubeconfig. Just secure, modern CI/CD.

Why This Matters

  • 🔒 No secrets stored in GitHub
  • 🔐 Short-lived, auditable credentials via OIDC
  • 🧑‍💻 First-class support from Google + GitHub
  • 🔁 Reusable pattern for any GCP service, not just GKE

Architecture

gha-gke-wif

Flow:

  1. GitHub Workflow uses OIDC token
  2. Google validates identity and issues short-lived credentials
  3. Workflow impersonates a GCP Service Account
  4. Runs kubectl or gcloud to deploy to GKE

Prerequisites

  • GCP project + billing enabled
  • A GKE Autopilot or Standard cluster
  • GitHub repo (private or public)
  • Terraform 1.3+ and gcloud CLI
  • Your app packaged into a container

Terraform Setup

New Autopilot cluster

provider "google" {
  project = var.project_id
  region  = var.region
}

resource "google_container_cluster" "autopilot" {
  name     = var.cluster_name
  location = var.region

  enable_autopilot = true

  workload_identity_config {
    workload_pool = "${var.project_id}.svc.id.goog"
  }

  release_channel {
    channel = "REGULAR"
  }

  ip_allocation_policy {}
  networking_mode = "VPC_NATIVE"
}

Workload Identity Federation

resource "google_iam_workload_identity_pool" "github_pool" {
  workload_identity_pool_id = "github"
  display_name              = "GitHub Actions Pool"
}

resource "google_iam_workload_identity_pool_provider" "github_provider" {
  workload_identity_pool_id = google_iam_workload_identity_pool.github_pool.workload_identity_pool_id
  workload_identity_pool_provider_id = "github-provider"
  display_name = "GitHub OIDC"
  attribute_mapping = {
    "google.subject"         = "assertion.sub"
    "attribute.repository"   = "assertion.repository"
  }

  oidc {
    issuer_uri = "https://token.actions.githubusercontent.com"
  }
}

resource "google_service_account" "gha_deployer" {
  account_id   = "gha-deployer"
  display_name = "GitHub Actions deployer"
}

resource "google_service_account_iam_member" "allow_impersonation" {
  service_account_id = google_service_account.gha_deployer.name
  role               = "roles/iam.workloadIdentityUser"
  member             = "principalSet://iam.googleapis.com/${google_iam_workload_identity_pool.github_pool.name}/attribute.repository/${var.github_repo}"
}

resource "google_project_iam_member" "deploy_permissions" {
  role   = "roles/container.developer"
  member = "serviceAccount:${google_service_account.gha_deployer.email}"
}

Variables

variable "project_id" {}
variable "region" {
  default = "us-central1"
}
variable "cluster_name" {
  default = "gha-ci-cluster"
}
variable "github_repo" {
  description = "GitHub repo in format owner/repo"
}

App deployment

apiVersion: apps/v1
kind: Deployment
metadata:
  name: web
spec:
  replicas: 1
  selector:
    matchLabels:
      app: web
  template:
    metadata:
      labels:
        app: web
    spec:
      containers:
      - name: web
        image: nginx
        ports:
        - containerPort: 80

GitHub Actions Setup

name: Deploy to GKE via Workload Identity

on:
  push:
    branches: [ main ]

permissions:
  id-token: write
  contents: read

jobs:
  deploy:
    runs-on: ubuntu-latest

    steps:
    - name: Checkout
      uses: actions/checkout@v3

    - name: Authenticate with Google Cloud
      uses: google-github-actions/auth@v1
      with:
        token_format: "id_token"
        workload_identity_provider: "projects/<PROJECT_NUM>/locations/global/workloadIdentityPools/github/providers/github-provider"
        service_account: "gha-deployer@<PROJECT_ID>.iam.gserviceaccount.com"

    - name: Setup gcloud
      uses: google-github-actions/setup-gcloud@v1

    - name: Configure kubectl
      run: |
        gcloud container clusters get-credentials ${{ secrets.CLUSTER_NAME }} --region ${{ secrets.REGION }}

    - name: Deploy app
      run: |
        kubectl apply -f k8s/

Test it

git push origin main
  • GitHub will authenticate using OIDC
  • GCP will issue a token
  • Workflow will deploy to GKE without a service account key

Key Takeaways

🔑 Workload Identity Federation = no key rotation, no secret sprawl

🛡 GitHub OIDC → GCP IAM is the new best practice

☁️ Works for GKE, Cloud Run, Cloud Storage, etc.

✅ GitHub auth + setup-gcloud = seamless GKE integration

Conclusion

You've now built a secure, automated CI/CD pipeline using GitHub Actions + GKE + Workload Identity Federation. This is production-grade infra without touching a single JSON/token key.

Give it a try and see how it can simplify your GKE deployments!

Related Posts