GitHub Actions con Google Cloud Run: CI/CD y permisos

Qué es GitHub Actions, cómo estructurar un workflow de CI/CD para Cloud Run, y cuáles son exactamente los permisos necesarios en GCP y GitHub para que todo funcione

Edgardo Silva
github-actionsgoogle-cloudcloud-runcicddevopsiam

Después de configurar Cloud Run manualmente con gcloud run deploy, el siguiente paso natural es automatizar ese proceso. Cada push a main debería construir la imagen, subirla a Artifact Registry y desplegar en Cloud Run — sin intervención manual.

GitHub Actions hace exactamente eso. Pero la parte difícil no es el workflow en sí, sino los permisos. GCP tiene un modelo IAM granular y si falta un solo rol, el pipeline falla.



¿Qué es GitHub Actions?

GitHub Actions es la plataforma de CI/CD integrada en GitHub. Permite definir workflows en archivos YAML dentro de .github/workflows/ que se ejecutan en respuesta a eventos del repositorio: un push, un pull request, un tag, un schedule.

Cada workflow tiene:

  • Trigger (on:): el evento que lo activa
  • Jobs: unidades de trabajo que corren en paralelo o secuencialmente
  • Steps: comandos o acciones dentro de cada job
  • Actions: pasos reutilizables publicados en el Marketplace

El entorno de ejecución son máquinas virtuales efímeras (runners) administradas por GitHub — Ubuntu, Windows o macOS. Para integraciones con GCP, todo ocurre desde esas VMs hacia la API de Google Cloud.


Estructura del workflow para Cloud Run

Un pipeline típico tiene tres fases: autenticación con GCP, build y push de la imagen Docker, y deploy en Cloud Run.

# .github/workflows/deploy.yml
name: Deploy to Cloud Run

on:
  push:
    branches:
      - main

env:
  PROJECT_ID: mi-proyecto-gcp
  REGION: us-central1
  SERVICE_NAME: mi-app
  REGISTRY: us-central1-docker.pkg.dev

jobs:
  deploy:
    runs-on: ubuntu-latest

    permissions:
      contents: read
      id-token: write  # necesario para Workload Identity Federation

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

      - name: Authenticate to Google Cloud
        uses: google-github-actions/auth@v2
        with:
          workload_identity_provider: ${{ secrets.WIF_PROVIDER }}
          service_account: ${{ secrets.WIF_SERVICE_ACCOUNT }}

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Configure Docker for Artifact Registry
        run: gcloud auth configure-docker ${{ env.REGISTRY }} --quiet

      - name: Build and push Docker image
        uses: docker/build-push-action@v5
        with:
          context: .
          push: true
          tags: ${{ env.REGISTRY }}/${{ env.PROJECT_ID }}/mi-repo/${{ env.SERVICE_NAME }}:${{ github.sha }}

      - name: Deploy to Cloud Run
        uses: google-github-actions/deploy-cloudrun@v2
        with:
          service: ${{ env.SERVICE_NAME }}
          region: ${{ env.REGION }}
          image: ${{ env.REGISTRY }}/${{ env.PROJECT_ID }}/mi-repo/${{ env.SERVICE_NAME }}:${{ github.sha }}

Autenticación: Workload Identity Federation vs Service Account Key

Hay dos formas de autenticar GitHub Actions con GCP:

MétodoSeguridadComplejidad
JSON Key (service account key)Baja — clave estática que puede filtrarseBaja — copiar y pegar en GitHub Secrets
Workload Identity FederationAlta — credenciales efímeras por OIDCMedia — requiere configuración en GCP

Google recomienda WIF. La idea es que GCP confíe en los tokens OIDC que emite GitHub, sin necesidad de claves permanentes. Por eso el workflow necesita id-token: write en los permisos del job.


Permisos necesarios en GCP

Esta es la parte que más tiempo toma. Se configura desde la consola de GCP o con gcloud en terminal. El Service Account que usa el workflow necesita exactamente estos roles:

Roles sobre el proyecto

# Autenticar con tu cuenta personal primero
gcloud auth login

# Crear el service account para el workflow
gcloud iam service-accounts create github-actions-sa \
  --display-name="GitHub Actions SA" \
  --project=mi-proyecto-gcp

export SA_EMAIL="github-actions-sa@mi-proyecto-gcp.iam.gserviceaccount.com"
export PROJECT_ID="mi-proyecto-gcp"

Luego asignar los roles uno por uno:

# Subir imágenes a Artifact Registry
gcloud projects add-iam-policy-binding $PROJECT_ID \
  --member="serviceAccount:$SA_EMAIL" \
  --role="roles/artifactregistry.writer"

# Desplegar y administrar servicios en Cloud Run
gcloud projects add-iam-policy-binding $PROJECT_ID \
  --member="serviceAccount:$SA_EMAIL" \
  --role="roles/run.admin"

# Leer y escribir en Cloud Storage (para el build context de Docker)
gcloud projects add-iam-policy-binding $PROJECT_ID \
  --member="serviceAccount:$SA_EMAIL" \
  --role="roles/storage.admin"

# Necesario para que Cloud Run pueda impersonar la SA al desplegar
gcloud projects add-iam-policy-binding $PROJECT_ID \
  --member="serviceAccount:$SA_EMAIL" \
  --role="roles/iam.serviceAccountUser"

Rol adicional: permitir que Cloud Run use la SA en runtime

Cloud Run ejecuta el contenedor bajo un service account (por defecto el Compute SA). Si usas un SA dedicado para el servicio, la SA de GitHub Actions necesita poder "actuar como" ese SA:

# SA que usa Cloud Run en runtime (puede ser el mismo o uno distinto)
export RUNTIME_SA="mi-runtime-sa@mi-proyecto-gcp.iam.gserviceaccount.com"

gcloud iam service-accounts add-iam-policy-binding $RUNTIME_SA \
  --member="serviceAccount:$SA_EMAIL" \
  --role="roles/iam.serviceAccountUser" \
  --project=$PROJECT_ID

Resumen de roles necesarios

RolPara qué
roles/artifactregistry.writerPush de imágenes Docker
roles/run.adminDeploy y actualización del servicio Cloud Run
roles/storage.adminBuild context y capas de Docker
roles/iam.serviceAccountUserImpersonar la SA al desplegar

Configurar Workload Identity Federation desde la consola

Si prefieres la consola web en lugar de la CLI:

  1. IAM & Admin → Workload Identity Federation → Create Pool

    • Name: github-pool
    • Provider: OIDC
    • Issuer URL: https://token.actions.githubusercontent.com
    • Attribute mapping:
      • google.subjectassertion.sub
      • attribute.repositoryassertion.repository
  2. Agregar condition (importante para limitar a tu repo):

    • attribute.repository == "tu-usuario/tu-repo"
  3. Conectar el Service Account al pool:

    • En la SA creada antes → Permissions → Grant access
    • Principal: principalSet://iam.googleapis.com/projects/PROJECT_NUMBER/locations/global/workloadIdentityPools/github-pool/attribute.repository/tu-usuario/tu-repo
    • Role: roles/iam.workloadIdentityUser

El valor de WIF_PROVIDER que va en GitHub Secrets tiene este formato:

projects/PROJECT_NUMBER/locations/global/workloadIdentityPools/github-pool/providers/github-provider

Permisos necesarios en GitHub

En el repositorio de GitHub también hay configuración:

  1. Settings → Actions → General → Workflow permissions

    • Activar "Read and write permissions"
    • O dejar en "Read repository contents" y manejar los permisos granularmente en el YAML con el bloque permissions:
  2. Settings → Secrets and variables → Actions → New repository secret

    • WIF_PROVIDER: el provider ID del Workload Identity Federation
    • WIF_SERVICE_ACCOUNT: el email del service account (github-actions-sa@...)

Si usas JSON key en lugar de WIF:

  • GCP_SA_KEY: el contenido completo del JSON de la clave

Error más común: falta iam.serviceAccountUser

El error que aparece cuando no se asignó roles/iam.serviceAccountUser:

ERROR: (gcloud.run.deploy) PERMISSION_DENIED: Permission 'iam.serviceaccounts.actAs'
denied on service account [...]

Esto pasa porque al desplegar, Cloud Run necesita "actuar como" el service account de runtime. La SA de GitHub Actions debe tener ese permiso explícitamente.


Conclusión

GitHub Actions para Cloud Run funciona bien una vez que los permisos están correctos. El workflow en sí es sencillo — la complejidad está en IAM. Los cuatro roles del service account son el mínimo necesario, y la condición en Workload Identity Federation es importante para que solo tu repositorio específico pueda autenticarse contra tu proyecto de GCP.