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
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étodo | Seguridad | Complejidad |
|---|---|---|
| JSON Key (service account key) | Baja — clave estática que puede filtrarse | Baja — copiar y pegar en GitHub Secrets |
| Workload Identity Federation | Alta — credenciales efímeras por OIDC | Media — 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
| Rol | Para qué |
|---|---|
roles/artifactregistry.writer | Push de imágenes Docker |
roles/run.admin | Deploy y actualización del servicio Cloud Run |
roles/storage.admin | Build context y capas de Docker |
roles/iam.serviceAccountUser | Impersonar la SA al desplegar |
Configurar Workload Identity Federation desde la consola
Si prefieres la consola web en lugar de la CLI:
-
IAM & Admin → Workload Identity Federation → Create Pool
- Name:
github-pool - Provider: OIDC
- Issuer URL:
https://token.actions.githubusercontent.com - Attribute mapping:
google.subject→assertion.subattribute.repository→assertion.repository
- Name:
-
Agregar condition (importante para limitar a tu repo):
attribute.repository == "tu-usuario/tu-repo"
-
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:
-
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:
-
Settings → Secrets and variables → Actions → New repository secret
WIF_PROVIDER: el provider ID del Workload Identity FederationWIF_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.