Una pipeline de GitHub Actions no necesita guardar una clave JSON de una cuenta de servicio para desplegar en Cloud Run. La opción más controlable es usar OpenID Connect (OIDC), de modo que cada ejecución solicite una credencial de corta duración mientras Google Cloud decide si ese repositorio puede asumir la identidad de despliegue.
El detalle que suele perderse es este: id-token: write permite que el workflow solicite un token OIDC, pero no concede acceso a Google Cloud. El acceso nace de la combinación entre la condición del Workload Identity Provider, la asociación que conecta el repositorio con la cuenta de servicio y los roles IAM de esa cuenta. La guía general de Cloud Run explica el modelo de la plataforma; este artículo se centra en el camino entre CI y el despliegue.
Después de la autenticación, la elección del ciclo de vida del servicio sigue siendo importante. La comparación de facturación por solicitudes o instancias en Cloud Run ayuda a separar la decisión de despliegue de la forma en que se ejecuta el servicio.
Los comandos y el YAML de abajo son ilustrativos. No utilicé un proyecto de Google Cloud, credenciales ni una pipeline real para escribir este artículo. Aplica la configuración primero en un proyecto de prueba y compárala con los nombres y las políticas de tu organización.
Respuesta breve
- Crea un Workload Identity Pool y un proveedor OIDC que acepte solo el repositorio esperado.
- Concede a la cuenta de servicio de despliegue la asociación
roles/iam.workloadIdentityUserpara ese repositorio.- Asigna los roles de Cloud Run, Artifact Registry y
Service Account Useren el ámbito necesario.- En el workflow, usa
id-token: write, autentica congoogle-github-actions/authy ejecutadeploy-cloudrundespués.- Comprueba el repositorio, la revisión y la URL publicada. Que exista un token no demuestra que el despliegue esté autorizado.
¿Qué cambia al sustituir la clave por OIDC?
Con una clave JSON, GitHub Secrets guarda una credencial de larga duración que la pipeline lee. Con OIDC, GitHub Actions solicita un JWT para esa ejecución y Google Cloud evalúa el token mediante un Workload Identity Provider. La guía de GitHub sobre OIDC en proveedores de nube describe la secuencia general: el proveedor debe confiar en el emisor, el workflow debe tener permiso para solicitar el token y la política debe limitar quién puede recibir acceso.
Google Cloud describe la federación de identidades como un intercambio entre el token firmado por el IdP, el Security Token Service y IAM. Para este caso, piensa en cuatro fronteras:
- GitHub emite un token para la ejecución.
- El proveedor comprueba que el token procede de un repositorio aceptado.
- La cuenta de servicio permite que esa identidad federada actúe en su nombre.
- Los roles de la cuenta de servicio limitan lo que puede hacer el despliegue.
Una condición que acepta solo la organización es mejor que ninguna, pero todavía puede permitir más repositorios de los previstos. Si un repositorio es el dueño del despliegue, limita el proveedor al valor completo ORG/REPO y repite esa restricción en la asociación IAM.
Crea un proveedor restringido al repositorio
Necesitas un proyecto, su número, un pool, un proveedor OIDC y una cuenta de servicio que actuará como deployer. El README actual de google-github-actions/auth recomienda mapear las claims usadas en las condiciones y advierte que el proveedor debe tener siempre una condición de atributo.
Define los valores en tu terminal. Son placeholders, no nombres que debas copiar literalmente:
export PROJECT_ID="tu-proyecto"
export PROJECT_NUMBER="123456789012"
export GITHUB_REPOSITORY="tu-org/tu-repo"
export POOL_ID="github"
export PROVIDER_ID="tu-repo"
export DEPLOYER_SA="github-deployer@${PROJECT_ID}.iam.gserviceaccount.com"
Crea la cuenta de servicio y el pool:
gcloud iam service-accounts create github-deployer \
--project="${PROJECT_ID}" \
--display-name="GitHub Actions Cloud Run deployer"
gcloud iam workload-identity-pools create "${POOL_ID}" \
--project="${PROJECT_ID}" \
--location="global" \
--display-name="GitHub Actions pool"
Ahora crea el proveedor. El mapeo deja disponible attribute.repository para la asociación IAM. La condición usa la claim repository para impedir que otro repositorio de la organización entre en el pool:
gcloud iam workload-identity-pools providers create-oidc "${PROVIDER_ID}" \
--project="${PROJECT_ID}" \
--location="global" \
--workload-identity-pool="${POOL_ID}" \
--display-name="GitHub Actions provider" \
--attribute-mapping="google.subject=assertion.sub,attribute.repository=assertion.repository,attribute.repository_owner=assertion.repository_owner,attribute.ref=assertion.ref" \
--attribute-condition="assertion.repository == '${GITHUB_REPOSITORY}'" \
--issuer-uri="https://token.actions.githubusercontent.com"
La expresión es deliberadamente estrecha. Si quieres permitir varios repositorios, registra esa decisión en la política y prueba cada combinación. No sustituyas la condición por un comodín solo para hacer desaparecer un error de autenticación.
El identificador completo del proveedor que irá al workflow contiene el número del proyecto, el pool y el proveedor:
projects/123456789012/locations/global/workloadIdentityPools/github/providers/tu-repo
Permite que el repositorio use el deployer
La condición del proveedor controla la entrada al pool. Todavía tienes que autorizar a la identidad federada a suplantar la cuenta de servicio. Esta asociación usa el repositorio mapeado en el pool, no toda la organización:
export WORKLOAD_IDENTITY_POOL="projects/${PROJECT_NUMBER}/locations/global/workloadIdentityPools/${POOL_ID}"
gcloud iam service-accounts add-iam-policy-binding "${DEPLOYER_SA}" \
--project="${PROJECT_ID}" \
--role="roles/iam.workloadIdentityUser" \
--member="principalSet://iam.googleapis.com/${WORKLOAD_IDENTITY_POOL}/attribute.repository/${GITHUB_REPOSITORY}"
El README de la action de autenticación muestra este modelo de federación mediante una cuenta de servicio. La action puede usar federación directa en recursos compatibles, pero el camino con un deployer dedicado hace más claros los roles que debe revisar una pipeline de Cloud Run.
Concede los roles de despliegue en el ámbito correcto
El deployer no necesita ser propietario del proyecto. Para publicar una imagen que ya existe en Artifact Registry en un servicio existente, la documentación actual sobre roles IAM de Cloud Run separa estas necesidades:
| Recurso | Rol usado en el ejemplo | Para qué sirve |
|---|---|---|
| Servicio de Cloud Run | roles/run.developer |
Crear o actualizar el servicio y la revisión dentro del ámbito concedido |
| Repositorio de Artifact Registry | roles/artifactregistry.reader |
Leer la imagen que se desplegará |
| Cuenta de servicio de runtime | roles/iam.serviceAccountUser |
Permitir que el deployer use la identidad elegida por el servicio |
El rol Service Account User merece atención. Permite que el deployer actúe como la identidad de runtime. Si esa identidad tiene permisos amplios, también crece el impacto de un workflow comprometido. Usa una cuenta de servicio de runtime separada y dale solo los roles que necesita la aplicación.
Los comandos dependen del nombre del servicio, la región y el repositorio. Este es un esqueleto de referencia:
export SERVICE="tu-servicio"
export REGION="us-central1"
export GAR_LOCATION="us-central1"
export REPOSITORY="containers"
export RUNTIME_SA="cloud-run-runtime@${PROJECT_ID}.iam.gserviceaccount.com"
gcloud run services add-iam-policy-binding "${SERVICE}" \
--project="${PROJECT_ID}" \
--region="${REGION}" \
--member="serviceAccount:${DEPLOYER_SA}" \
--role="roles/run.developer"
gcloud artifacts repositories add-iam-policy-binding "${REPOSITORY}" \
--project="${PROJECT_ID}" \
--location="${GAR_LOCATION}" \
--member="serviceAccount:${DEPLOYER_SA}" \
--role="roles/artifactregistry.reader"
gcloud iam service-accounts add-iam-policy-binding "${RUNTIME_SA}" \
--project="${PROJECT_ID}" \
--member="serviceAccount:${DEPLOYER_SA}" \
--role="roles/iam.serviceAccountUser"
Si la pipeline también publica la imagen, el deployer necesita permiso de escritura en el repositorio. Eso es un cambio de ámbito, no un detalle de OIDC. Separa el rol de publicación del rol de runtime y no concedas artifactregistry.admin solo porque la primera prueba falló.
Escribe el workflow de GitHub Actions
El workflow debe declarar id-token: write, hacer checkout del repositorio antes de autenticarse y pasar el proveedor y la cuenta de servicio a la action. La action deploy-cloudrun documenta el uso conjunto de auth@v3 y deploy-cloudrun@v3. El ejemplo usa checkout@v7, auth@v3 y deploy-cloudrun@v3, versiones que aparecen en la documentación actual consultada el 11/11/2026.
name: Deploy Cloud Run
on:
push:
branches: [main]
env:
PROJECT_ID: $
REGION: $
SERVICE: $
IMAGE: $
jobs:
deploy:
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
steps:
- name: Check out the repository
uses: actions/checkout@v7
- name: Authenticate to Google Cloud
uses: google-github-actions/auth@v3
with:
project_id: $
workload_identity_provider: $
service_account: $
- name: Deploy the existing image
id: deploy
uses: google-github-actions/deploy-cloudrun@v3
with:
project_id: $
region: $
service: $
image: $
- name: Check the deployed service
run: curl --fail --silent --show-error "$/health"
GCP_WIF_PROVIDER debe contener el identificador completo, no solo el nombre github. GCP_DEPLOYER_SERVICE_ACCOUNT debe apuntar a la cuenta de servicio que recibió la asociación. Una variable equivocada puede producir un mensaje genérico de permisos, así que registra estos valores en el inventario de la pipeline sin imprimir tokens ni credenciales.
El workflow supone que $ ya apunta a una imagen de Artifact Registry. Si el mismo job también construye y publica la imagen, autentica Docker con un access token de corta duración y concede roles/artifactregistry.writer al deployer. La documentación de docker/login-action muestra este patrón con oauth2accesstoken; no uses una clave JSON del registry como atajo.
El archivo que crea la action de autenticación también merece una regla en el repositorio:
gha-creds-*.json
El README de google-github-actions/auth recomienda ignorar este patrón cuando la action crea un archivo de credenciales para herramientas posteriores. El archivo es temporal, pero una regla en .gitignore reduce el riesgo de empaquetar el artefacto en una imagen o commit.
¿Cómo verificas el despliegue sin confiar en el check verde?
Verifica por capas. Un job verde demuestra que los pasos terminaron sin error. No demuestra que el servicio recibió la imagen correcta ni que el endpoint principal responde.
- Confirma la identidad y el ámbito. Revisa la organización, el repositorio y la rama que acepta la condición del proveedor.
id-token: writepor sí solo no concede ningún rol de Google Cloud. - Confirma la revisión. Después del despliegue, compara la imagen y el digest que muestra
gcloud run services describecon la imagen que la pipeline calculó para ese commit. - Prueba un endpoint seguro. Usa un health check que no escriba en una base de datos ni encole trabajo. Si el servicio es privado, configura la autenticación de la prueba sin hacerlo público.
- Revisa los logs. Una respuesta 200 de
curlno demuestra que una dependencia importante esté sana. Observa el arranque, la autorización, la lectura de la imagen y el flujo principal de la aplicación. - Demuestra el rollback. La action de despliegue de Cloud Run expone la URL del servicio como output. Registra la revisión anterior y conoce el comando o workflow que la restaura antes de promover un cambio mayor. Para decidir sobre el tráfico, consulta la guía de despliegue canary en Cloud Run.
Esta secuencia separa autenticación, autorización, entrega del artefacto y comportamiento de la aplicación. Cuando todo se reduce a “el job pasó”, es más difícil localizar el primer fallo.
Diagnostica los errores más comunes
El proveedor rechaza el token
Comprueba el issuer https://token.actions.githubusercontent.com, el mapeo de assertion.repository y la condición del proveedor. Un repositorio renombrado, un fork o un workflow ejecutado en otro contexto puede no cumplir la expresión. No elimines la condición para probar; corrige la claim que debe aceptarse.
El token se emite, pero se deniega la cuenta de servicio
Esto suele indicar que el principal del pool no tiene roles/iam.workloadIdentityUser en la cuenta de servicio, o que el principalSet usa el número de proyecto, pool o atributo equivocado. Compara el identificador completo del pool con la asociación y confirma que la ruta del atributo usa exactamente attribute.repository/ORG/REPO.
El deployer no puede actualizar el servicio
La cuenta de servicio autenticada puede ser válida y aun así no tener el rol en el recurso correcto. Comprueba roles/run.developer en el servicio o proyecto, roles/artifactregistry.reader en el repositorio de la imagen y roles/iam.serviceAccountUser en la identidad de runtime. La action no sustituye esos roles.
La imagen existe, pero Cloud Run no puede usarla
Separa la identidad que publica la imagen de la identidad que ejecuta el contenedor. El deployer debe leer el repositorio durante el despliegue. La identidad de runtime necesita los accesos de la aplicación, pero no debe recibir la clave del deployer. Si el error aparece solo en una región, revisa también el nombre completo del repositorio y el proyecto de la imagen.
El ejemplo funciona manualmente, pero falla en GitHub
Tu terminal local usa la identidad de tu sesión. El workflow usa el principal federado y la condición del proveedor. Repite el diagnóstico con el repositorio, la rama, el environment y la cuenta de servicio reales de la ejecución. No guardes la clave local en un secret solo para confirmar los roles: eso oculta el problema que la migración quería eliminar.
¿Cuándo no basta este modelo?
OIDC elimina la clave JSON almacenada, pero no elimina la necesidad de una política de confianza. Un proveedor amplio, una cuenta de servicio administrativa o un workflow que acepta código sin revisar siguen siendo riesgos.
Si el job construye, publica, despliega, ejecuta una migración y cambia el tráfico, considera separar esas operaciones en jobs con permisos diferentes. Si la organización exige aprobación antes de producción, usa un environment protegido y conecta la condición de despliegue con ese flujo. Si el recurso no acepta identidades federadas directamente, usa federación mediante una cuenta de servicio con una asociación explícita y documenta la excepción.
Mantén también separada la identidad del deployer de la identidad del contenedor. La primera publica una revisión. La segunda existe mientras el servicio se ejecuta y accede a Pub/Sub, Secret Manager, Cloud SQL u otro recurso. Mezclarlas dificulta el rollback y la investigación de incidentes.
El valor de un despliegue sin clave no está en sustituir un bloque YAML. Está en hacer que Google Cloud responda una pregunta concreta antes de cada publicación: ¿este repositorio, en este contexto, puede asumir esta identidad para realizar exactamente este despliegue? Si la respuesta no aparece en la condición y los roles, la pipeline todavía depende de confianza implícita.