Uma pipeline do GitHub Actions não precisa guardar uma chave JSON de service account para fazer deploy no Cloud Run. O caminho mais controlável é usar OpenID Connect (OIDC) para que cada execução peça uma credencial curta, enquanto o Google Cloud decide se aquele repositório pode assumir a identidade de deploy.
O ponto que costuma ser perdido é este: id-token: write permite que o workflow peça um token OIDC, mas não dá acesso ao Google Cloud. O acesso nasce da combinação entre a condição do Workload Identity Provider, o binding que liga o repositório ao service account e os papéis IAM desse service account. A introdução ao Google Cloud Run explica o modelo da plataforma; aqui o foco é o caminho entre CI e deploy.
Depois da autenticação, a escolha do ciclo de vida ainda importa. O comparativo de faturamento por solicitações ou instâncias no Cloud Run ajuda a separar o deploy da decisão sobre como o serviço roda.
Os comandos e o YAML abaixo são ilustrativos. Não usei um projeto Google Cloud, credenciais ou uma pipeline real para escrever este artigo. A configuração deve ser aplicada primeiro em um projeto de teste e conferida com os nomes e políticas da sua organização.
Resposta curta
- Crie um Workload Identity Pool e um provider OIDC que aceite apenas o repositório esperado.
- Dê ao service account de deploy o binding
roles/iam.workloadIdentityUserpara esse repositório.- Conceda os papéis de Cloud Run, Artifact Registry e
Service Account Userno escopo necessário.- No workflow, use
id-token: write, autentique comgoogle-github-actions/authe só depois executedeploy-cloudrun.- Verifique o repositório, a revisão e a URL publicada. O fato de o token existir não prova que o deploy está autorizado.
O que muda quando você troca a chave por OIDC?
Com uma chave JSON, o GitHub Secrets guarda uma credencial de longa duração que a pipeline lê. Com OIDC, o GitHub Actions pede um JWT para aquela execução e o Google Cloud avalia o token por meio de um Workload Identity Provider. O guia do GitHub sobre OIDC em provedores de nuvem descreve essa sequência em termos gerais: o provedor precisa confiar no emissor, o workflow precisa ter a permissão para pedir o token e a política precisa restringir quem pode receber acesso.
O Google Cloud explica a federação de identidade como uma troca entre o token assinado pelo IdP, o Security Token Service e o IAM. Para este caso, pense em quatro fronteiras:
- O GitHub emite um token para a execução.
- O provider verifica que o token veio de um repositório aceito.
- O service account autoriza aquela identidade federada a agir em seu nome.
- Os papéis do service account limitam o que o deploy pode fazer.
Uma condição que aceita apenas a organização é melhor que nenhuma condição, mas ainda pode permitir mais repositórios do que você pretendia. Se o deploy pertence a um único repositório, restrinja o provider ao valor completo de ORG/REPO e repita essa restrição no binding IAM.
Crie um provider restrito ao repositório
Você precisa de um projeto, do número desse projeto, de um pool, de um provider OIDC e de um service account que será o deployer. O README atual da action google-github-actions/auth recomenda mapear as claims que serão usadas nas condições e alerta para sempre adicionar uma condição de atributo ao provider.
Defina os valores no seu terminal. Eles são placeholders, não nomes que você deve copiar literalmente:
export PROJECT_ID="seu-projeto"
export PROJECT_NUMBER="123456789012"
export GITHUB_REPOSITORY="sua-org/seu-repo"
export POOL_ID="github"
export PROVIDER_ID="seu-repo"
export DEPLOYER_SA="github-deployer@${PROJECT_ID}.iam.gserviceaccount.com"
Crie o service account e o 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"
Agora crie o provider. O mapeamento deixa attribute.repository disponível para o binding IAM. A condição usa a claim repository para impedir que outro repositório da plataforma entre no 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"
A expressão é deliberadamente estreita. Se você quiser permitir vários repositórios, liste essa decisão na política e teste cada combinação. Não troque a condição por um curinga só para fazer um erro de autenticação desaparecer.
O provider completo que vai para o workflow tem o número do projeto, o pool e o provider:
projects/123456789012/locations/global/workloadIdentityPools/github/providers/seu-repo
Permita que o repositório use o deployer
A condição do provider controla a entrada no pool. Ainda falta autorizar a identidade federada a impersonar o service account. O binding abaixo usa o repositório mapeado no pool, não toda a organização:
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}"
O README da action de autenticação mostra esse modelo de federação por meio de service account. A action pode usar federação direta em recursos compatíveis, mas o caminho com um deployer dedicado torna a revisão dos papéis mais clara para uma pipeline de Cloud Run.
Conceda os papéis de deploy no escopo certo
O deployer não precisa ser proprietário do projeto. Para publicar uma imagem que já existe no Artifact Registry em um serviço existente, a documentação atual de papéis IAM do Cloud Run separa estas necessidades:
| Recurso | Papel usado no exemplo | Para que serve |
|---|---|---|
| Serviço Cloud Run | roles/run.developer |
Criar ou atualizar o serviço e a revisão dentro do escopo concedido |
| Repositório Artifact Registry | roles/artifactregistry.reader |
Ler a imagem que será implantada |
| Service account de runtime | roles/iam.serviceAccountUser |
Permitir que o deployer use a identidade escolhida pelo serviço |
O papel Service Account User merece atenção. Ele permite agir como a identidade de runtime. Se essa identidade tem permissões amplas, o impacto de um workflow comprometido também cresce. Use um service account de runtime separado e dê a ele apenas os papéis que a aplicação precisa.
Os comandos de concessão dependem de nomes de serviço, região e repositório. Um esqueleto de referência é:
export SERVICE="seu-servico"
export REGION="southamerica-east1"
export GAR_LOCATION="southamerica-east1"
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"
Se a pipeline também fizer push da imagem, o deployer precisa de uma permissão de escrita no repositório. Essa é uma mudança de escopo, não um detalhe do OIDC. Separe o papel de publicação do papel de runtime e não dê artifactregistry.admin apenas porque o primeiro teste falhou.
Escreva o workflow do GitHub Actions
O workflow precisa declarar id-token: write, fazer checkout antes da autenticação e passar o provider e o service account para a action. A action deploy-cloudrun documenta o uso conjunto de auth@v3 e deploy-cloudrun@v3. O exemplo abaixo usa checkout@v7, auth@v3 e deploy-cloudrun@v3, versões mostradas nas documentações atuais consultadas em 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 deve conter o identificador completo, e não apenas o nome github. GCP_DEPLOYER_SERVICE_ACCOUNT deve apontar para o service account que recebeu o binding. Uma variável trocada pode produzir uma mensagem genérica de permissão, então registre esses valores no inventário da pipeline sem imprimir tokens ou credenciais.
O workflow assume que $ já aponta para uma imagem existente no Artifact Registry. Se o mesmo job também fizer build e push, autentique o Docker com um access token temporário e conceda roles/artifactregistry.writer ao deployer. A documentação do docker/login-action mostra esse padrão com oauth2accesstoken; não use uma chave JSON de registry como atalho.
O arquivo gerado pela action de autenticação também merece uma regra no repositório:
gha-creds-*.json
O README de google-github-actions/auth recomenda ignorar esse padrão quando a action cria o arquivo de credenciais para ferramentas posteriores. O arquivo é temporário, mas uma regra no .gitignore reduz o risco de empacotar o artefato em uma imagem ou commit.
Como verificar o deploy sem confiar no verde da pipeline?
Faça a verificação em camadas. Um job verde prova que os passos terminaram sem erro, não que o serviço recebeu a imagem certa nem que o endpoint principal responde.
- Confirme a identidade e o escopo. Revise a organização, o repositório e o branch que a condição do provider aceita.
id-token: writesozinho não concede nenhum papel no Google Cloud. - Confirme a revisão. Depois do deploy, compare a imagem e o digest exibidos por
gcloud run services describecom a imagem que a pipeline calculou para aquele commit. - Teste um endpoint seguro. Use um health check que não escreva no banco nem dispare uma fila. Se o serviço é privado, configure a autenticação do teste sem transformar o serviço em público.
- Confira os logs. Um
curlque responde 200 não prova que uma dependência importante está saudável. Observe startup, autorização, leitura da imagem e o caminho principal da aplicação. - Prove o rollback. A página de deploy do Cloud Run expõe a URL do serviço como output. Registre a revisão anterior e saiba qual comando ou workflow a restaura antes de promover uma mudança maior. Para a decisão de tráfego, veja também o guia de deploy canário no Cloud Run.
Essa sequência separa autenticação, autorização, entrega do artefato e comportamento da aplicação. Quando tudo é reduzido a “o job passou”, a primeira falha fica difícil de localizar.
Diagnóstico dos erros mais comuns
O provider rejeita o token
Confira o issuer https://token.actions.githubusercontent.com, o mapeamento de assertion.repository e a condição do provider. Um repositório renomeado, um fork ou um workflow disparado em outro contexto pode não satisfazer a expressão. Não remova a condição para testar: corrija a claim que deveria ser aceita.
O token é emitido, mas o service account é negado
Isso normalmente indica que o principal do pool não tem roles/iam.workloadIdentityUser no service account, ou que o principalSet usa o número do projeto, pool ou atributo errado. Compare o identificador completo do pool com o valor do binding e confirme que o caminho do atributo usa exatamente attribute.repository/ORG/REPO.
O deployer não pode atualizar o serviço
O service account autenticado pode ser válido e ainda não ter o papel no recurso certo. Verifique roles/run.developer no serviço ou projeto, roles/artifactregistry.reader no repositório da imagem e roles/iam.serviceAccountUser na identidade de runtime. A action não substitui esses papéis.
A imagem existe, mas o Cloud Run não consegue usá-la
Separe quem publica a imagem de quem executa o container. O deployer precisa ler o repositório durante o deploy. A identidade usada em runtime precisa ter os acessos da aplicação, mas não deve receber a chave do deployer. Se o erro só aparece em uma região, confira também o nome completo do repositório e o projeto da imagem.
O exemplo funciona manualmente, mas falha no GitHub
O terminal local usa a identidade da sua sessão. O workflow usa o principal federado e a condição do provider. Refaça o diagnóstico com o repositório, branch, environment e service account reais da execução. Não coloque a chave local em um secret apenas para confirmar que os papéis estão corretos, pois isso mascara o problema que a migração queria eliminar.
Quando este modelo não é suficiente?
OIDC elimina a chave JSON armazenada, mas não elimina a necessidade de uma política de confiança. Um provider amplo, um service account com permissões administrativas ou um workflow que aceita código não revisado continua sendo um risco.
Se o job faz build, push, deploy, migração e alteração de tráfego, considere separar essas operações em jobs com permissões distintas. Se a organização exige aprovação antes da produção, use um environment protegido e associe a condição de deploy a esse fluxo. Se o recurso não aceita a identidade federada diretamente, use a federação por service account com o binding explícito e documente a exceção.
Também não confunda a identidade do deployer com a identidade do container. A primeira publica a revisão. A segunda existe quando o serviço roda e acessa Pub/Sub, Secret Manager, Cloud SQL ou outro recurso. Misturar as duas torna o rollback e a investigação de incidentes mais difíceis.
O ganho do deploy sem chave não está em trocar um bloco YAML. Está em fazer o Google Cloud responder a uma pergunta específica antes de cada publicação: este repositório, neste contexto, pode assumir esta identidade para fazer exatamente este deploy? Se a resposta não estiver expressa na condição e nos papéis, a pipeline ainda depende de confiança implícita.