Skip to main content

GitHub Actions Cloud Run Deployment Setup (Workload Identity Federation)

Purpose

Set up automated Cloud Run deployments via GitHub Actions using Workload Identity Federation (WIF) instead of long-lived JSON keys.

When to run this

  • Initial CI/CD setup for GitHub Actions → Cloud Run
  • Migration from JSON keys to WIF
  • Troubleshooting auth/permission/deploy failures in workflow runs

Prerequisites

  • GCP project payroll-bi-gauntlet exists
  • gcloud CLI installed and authenticated
  • GitHub repository: payroll-pipeline-cbs
  • GitHub Actions enabled for the repository

Inputs

  • Service: payroll-pipeline-cbs-api
  • Project: payroll-bi-gauntlet
  • Region: us-central1
  • Runtime Service Account: sa-worker@payroll-bi-gauntlet.iam.gserviceaccount.com
  • Authentication: Workload Identity Federation (no JSON keys)

Procedure

1) Create deployment service account

# Set variables
PROJECT_ID="payroll-bi-gauntlet"
SERVICE_ACCOUNT_NAME="sa-github-deployer"
SERVICE_ACCOUNT_EMAIL="${SERVICE_ACCOUNT_NAME}@${PROJECT_ID}.iam.gserviceaccount.com"

# Create service account
gcloud iam service-accounts create ${SERVICE_ACCOUNT_NAME} \
  --project=${PROJECT_ID} \
  --display-name="GitHub Actions Cloud Run Deployer" \
  --description="Service account for GitHub Actions to deploy Cloud Run services"

# Grant required IAM roles
gcloud projects add-iam-policy-binding ${PROJECT_ID} \
  --member="serviceAccount:${SERVICE_ACCOUNT_EMAIL}" \
  --role="roles/run.admin"

# Allow service account to act as runtime service account
gcloud iam service-accounts add-iam-policy-binding sa-worker@${PROJECT_ID}.iam.gserviceaccount.com \
  --member="serviceAccount:${SERVICE_ACCOUNT_EMAIL}" \
  --role="roles/iam.serviceAccountUser" \
  --project=${PROJECT_ID}

# Grant Cloud Build access (needed for Cloud Build deployments)
gcloud projects add-iam-policy-binding ${PROJECT_ID} \
  --member="serviceAccount:${SERVICE_ACCOUNT_EMAIL}" \
  --role="roles/cloudbuild.builds.editor" \
  --condition=None

# Grant Storage access (needed for source uploads)
gcloud projects add-iam-policy-binding ${PROJECT_ID} \
  --member="serviceAccount:${SERVICE_ACCOUNT_EMAIL}" \
  --role="roles/storage.admin" \
  --condition=None

# Grant Artifact Registry access (needed if using --source deployments, or for Cloud Build image pushes)
gcloud projects add-iam-policy-binding ${PROJECT_ID} \
  --member="serviceAccount:${SERVICE_ACCOUNT_EMAIL}" \
  --role="roles/artifactregistry.writer" \
  --condition=None

# Grant Service Usage Consumer role (required for --source deployments)
gcloud projects add-iam-policy-binding ${PROJECT_ID} \
  --member="serviceAccount:${SERVICE_ACCOUNT_EMAIL}" \
  --role="roles/serviceusage.serviceUsageConsumer" \
  --condition=None

# Grant Cloud Build service account permissions for --source deployments
# Get the default compute service account (used by Cloud Build for --source)
PROJECT_NUMBER=$(gcloud projects describe ${PROJECT_ID} --format="value(projectNumber)")
COMPUTE_SA="${PROJECT_NUMBER}-compute@developer.gserviceaccount.com"

# Grant Cloud Build service account permissions to push images and deploy
gcloud projects add-iam-policy-binding ${PROJECT_ID} \
  --member="serviceAccount:${COMPUTE_SA}" \
  --role="roles/artifactregistry.writer" \
  --condition=None

gcloud projects add-iam-policy-binding ${PROJECT_ID} \
  --member="serviceAccount:${COMPUTE_SA}" \
  --role="roles/run.admin" \
  --condition=None

# Grant Cloud Build service accounts permission to act as runtime service account
# This is required for --source deployments to set the service account
# Both the compute SA (used by default) and Cloud Build SA need this permission
gcloud iam service-accounts add-iam-policy-binding sa-worker@${PROJECT_ID}.iam.gserviceaccount.com \
  --member="serviceAccount:${COMPUTE_SA}" \
  --role="roles/iam.serviceAccountUser" \
  --project=${PROJECT_ID}

# Also grant to Cloud Build service account (may be used in some scenarios)
gcloud iam service-accounts add-iam-policy-binding sa-worker@${PROJECT_ID}.iam.gserviceaccount.com \
  --member="serviceAccount:${PROJECT_NUMBER}@cloudbuild.gserviceaccount.com" \
  --role="roles/iam.serviceAccountUser" \
  --project=${PROJECT_ID}

2) Create workload identity pool and provider

# Set variables
PROJECT_ID="payroll-bi-gauntlet"
POOL_ID="github-actions-pool"
PROVIDER_ID="github-provider"
POOL_NAME="projects/${PROJECT_ID}/locations/global/workloadIdentityPools/${POOL_ID}"
PROVIDER_NAME="${POOL_NAME}/providers/${PROVIDER_ID}"

# Enable required APIs
gcloud services enable \
  iamcredentials.googleapis.com \
  sts.googleapis.com \
  --project=${PROJECT_ID}

# Create workload identity pool
gcloud iam workload-identity-pools create ${POOL_ID} \
  --project=${PROJECT_ID} \
  --location="global" \
  --display-name="GitHub Actions Pool"

# Create OIDC provider for GitHub
gcloud iam workload-identity-pools providers create-oidc ${PROVIDER_ID} \
  --project=${PROJECT_ID} \
  --location="global" \
  --workload-identity-pool=${POOL_ID} \
  --display-name="GitHub Provider" \
  --attribute-mapping="google.subject=assertion.sub,attribute.actor=assertion.actor,attribute.repository=assertion.repository" \
  --issuer-uri="https://token.actions.githubusercontent.com"

3) Configure IAM binding (restrict to repository)

# Set variables
PROJECT_ID="payroll-bi-gauntlet"
POOL_ID="github-actions-pool"
PROVIDER_ID="github-provider"
SERVICE_ACCOUNT_EMAIL="sa-github-deployer@${PROJECT_ID}.iam.gserviceaccount.com"
REPO="abundy1/payroll-pipeline-cbs"  # Replace with your GitHub org/repo
BRANCH="main"

# Get provider resource name
PROVIDER_NAME=$(gcloud iam workload-identity-pools providers describe ${PROVIDER_ID} \
  --project=${PROJECT_ID} \
  --location="global" \
  --workload-identity-pool=${POOL_ID} \
  --format="value(name)")

# Allow GitHub Actions from specific repo/branch to impersonate service account
gcloud iam service-accounts add-iam-policy-binding ${SERVICE_ACCOUNT_EMAIL} \
  --project=${PROJECT_ID} \
  --role="roles/iam.workloadIdentityUser" \
  --member="principalSet://iam.googleapis.com/${PROVIDER_NAME}/attribute.repository/${REPO}"
Security Note: The binding above restricts access to the specific repository. To further restrict to a specific branch, use:
# More restrictive: specific branch only
gcloud iam service-accounts add-iam-policy-binding ${SERVICE_ACCOUNT_EMAIL} \
  --project=${PROJECT_ID} \
  --role="roles/iam.workloadIdentityUser" \
  --member="principalSet://iam.googleapis.com/${PROVIDER_NAME}/attribute.repository/${REPO}/attribute.ref/${BRANCH}"

4) Configure GitHub secrets

Go to your GitHub repository → Settings → Secrets and variables → Actions → New repository secret Add the following secrets:
Secret NameValueHow to Get
WIF_PROVIDERFull provider resource nameRun: gcloud iam workload-identity-pools providers describe github-provider --project=payroll-bi-gauntlet --location=global --workload-identity-pool=github-actions-pool --format="value(name)"
WIF_SERVICE_ACCOUNTService account emailsa-github-deployer@payroll-bi-gauntlet.iam.gserviceaccount.com
Example WIF_PROVIDER value:
projects/238826317621/locations/global/workloadIdentityPools/github-actions-pool/providers/github-provider

5) Verify setup

Verification

# Verify service account exists
gcloud iam service-accounts describe sa-github-deployer@payroll-bi-gauntlet.iam.gserviceaccount.com \
  --project=payroll-bi-gauntlet

# Verify workload identity pool exists
gcloud iam workload-identity-pools describe github-actions-pool \
  --project=payroll-bi-gauntlet \
  --location=global

# Verify provider exists
gcloud iam workload-identity-pools providers describe github-provider \
  --project=payroll-bi-gauntlet \
  --location=global \
  --workload-identity-pool=github-actions-pool

# Verify IAM bindings
gcloud iam service-accounts get-iam-policy sa-github-deployer@payroll-bi-gauntlet.iam.gserviceaccount.com \
  --project=payroll-bi-gauntlet
The workflow file .github/workflows/deploy_cloudrun.yml is already configured with:
  • Trigger: Push to main branch (when API files change)
  • Authentication: WIF via google-github-actions/auth@v2
  • Deployment: gcloud run deploy --source .
  • SHA Verification: Automatically verifies /api/health returns correct git_commit_sha

Failure modes & fixes

Workflow fails with “Permission denied”

  • Verify WIF_PROVIDER secret matches the full resource name
  • Verify WIF_SERVICE_ACCOUNT secret matches the service account email
  • Check IAM bindings: gcloud iam service-accounts get-iam-policy sa-github-deployer@payroll-bi-gauntlet.iam.gserviceaccount.com

Workflow fails with “Service account not found”

  • Verify service account exists: gcloud iam service-accounts describe sa-github-deployer@payroll-bi-gauntlet.iam.gserviceaccount.com
  • Check project ID matches: gcloud config get-value project

SHA verification fails

  • Check /api/health endpoint returns git_commit_sha field
  • Verify GIT_COMMIT_SHA environment variable is set correctly in deployment
  • Check Cloud Run logs for errors: gcloud logging read "resource.type=cloud_run_revision" --limit=50

Deployment times out

  • Increase timeout-minutes in workflow if needed
  • Check Cloud Build quotas: gcloud compute project-info describe --project=payroll-bi-gauntlet

Artifacts produced

  • Deployer service account and IAM bindings
  • Workload identity pool/provider configuration
  • GitHub Actions repository secrets (WIF_PROVIDER, WIF_SERVICE_ACCOUNT)
  • Successful workflow run and Cloud Run revision with SHA verification

Supporting reference

Required IAM roles explained

RolePurpose
roles/run.adminCreate/update Cloud Run services and revisions
roles/iam.serviceAccountUserAct as runtime service account (sa-worker)
roles/cloudbuild.builds.editorSubmit Cloud Build jobs (needed for --source deployments)
roles/storage.adminUpload source code to Cloud Storage (needed for --source deployments)

Security best practices

  1. Repository Restriction: WIF binding restricts access to abundy1/payroll-pipeline-cbs only
  2. Branch Restriction (optional): Can further restrict to main branch only
  3. Least Privilege: Service account has only the minimum required roles
  4. No JSON Keys: WIF eliminates the need for long-lived credentials
  5. Audit Trail: All deployments are logged in Cloud Run and GitHub Actions

Migration from JSON keys

If you’re currently using GCP_SA_KEY secret:
  1. Complete the WIF setup above
  2. Update .github/workflows/deploy_cloudrun.yml (already done)
  3. Add WIF_PROVIDER and WIF_SERVICE_ACCOUNT secrets
  4. Test deployment via workflow_dispatch
  5. Remove GCP_SA_KEY secret after verification