# Deploying to Cloud Run from GitHub without keys: Workload Identity Federation step by step

> GitHub Actions can deploy to Cloud Run with a per-run token instead of a JSON key. The pool, provider, deployer account, roles and workflow of Workload Identity Federation, and the errors you meet.

Published 2026-10-12 · Updated 2026-10-13 · [By Exodus team](https://exodus.elim.io/about) · Operations
<https://exodus.elim.io/blog/deploying-from-github-without-keys>

Deploying to Cloud Run from GitHub without keys uses Workload Identity Federation: each GitHub Actions run trades its signed OIDC token, valid about 5 minutes, for a Google access token that lasts one hour, so no JSON key exists to leak or rotate. The setup is a pool, a GitHub provider that admits your organization by ID, one binding for the main branch, and three roles. It costs nothing.

## In short

- GitHub's token lasts about 5 minutes and the deployer's Google token 1 hour, at most 12 where an organization policy allows.
- Organizations created on or after May 3, 2024 have service account key creation switched off by default.
- The deployer gets 3 roles on single resources: Cloud Run Developer on the service, Artifact Registry Writer on the image repository, Service Account User on the runtime account.
- Repositories created after July 15, 2026 get a new subject format, so grant on the repository ID and the ref, not the subject.
- IAM is free, and 30 deploys a month of 6 minutes use 180 of a private repository's 2,000 free GitHub minutes.

## Why not a key

A pipeline that deploys to Google Cloud has to prove who it is. A service account is the identity a program uses in Google Cloud, as a user account is for a person. The old way to sign one in is a key: a JSON file you download once, paste into a GitHub secret and forget. It stays valid until someone deletes it, wherever copies of it ended up.

Google's page on service account keys is plain about it: a leaked password usually still meets 2-step verification, a leaked key does not, and keys created in IAM have no expiry time by default. Attackers scan public repositories for them, and a private one can become public.

Organizations created on or after May 3, 2024 also have key creation and upload switched off by an organization policy, so a tutorial that begins with "download the JSON key" fails there. [Setting up a Google Cloud project for a small team](https://exodus.elim.io/blog/setting-up-a-google-cloud-project-for-a-small-team) covers that baseline; this guide is the step it points to.

Instead, every run of GitHub Actions, GitHub's built-in automation, can ask GitHub for a signed token that says which organization, repository, branch and workflow it is. Google Cloud checks it against rules you write and hands back a Google token. Nothing is stored anywhere.

## How the keyless sign-in works

![Three lanes: in GitHub a job gets a signed OIDC token valid about 5 minutes; in Google IAM the Security Token Service checks it and returns a federated token, which the IAM Credentials API turns into a one-hour token for the deployer account; in your project that token pushes the image and deploys to Cloud Run](https://exodus.elim.io/media/v2026-09-16/t/exodus/assets/image-53b2e69f80d14dbe0166acf44478134d1241c44f0165e5b298acb09e922c7a37-680x410-svg/bytes "One run, two exchanges: GitHub's token for a federated token, then the federated token for the deployer's one-hour access token.")

Four things take part.

**GitHub's OIDC token.** OIDC (OpenID Connect) is a standard for signed tokens that say who the holder is. A job that has the permission `id-token: write` can ask GitHub for one. GitHub signs it, and its claims, the fields inside, describe the run: `repository`, `repository_id`, `repository_owner`, `repository_owner_id`, `ref`, the branch or tag (such as `refs/heads/main`),, `job_workflow_ref` and, when the job uses one, `environment`. GitHub's docs say the permission only allows fetching the token. The `google-github-actions/auth` README says the token expires in 5 minutes.

**The pool and provider.** A pool holds identities from outside Google. A provider in it says "trust tokens signed by GitHub Actions", maps the token's claims to attributes Google can read, and holds an **attribute condition**: an expression that must be true or the token is refused. GitHub signs the tokens of every organization on github.com with one issuer (the address named in the token as its signer), so without a condition any GitHub repository in the world could get through this door. Google's page says you must use one.

**The Security Token Service.** The auth action sends GitHub's token to Google's Security Token Service, which checks the signature, the issuer, the audience (the service the token is meant for, by default the provider's own URL) and your condition, and returns a short-lived federated token.

**The deployer service account.** For Cloud Run the federated token alone is not enough: Google's list of products and limitations says Cloud Run does not support direct resource access by federated identities and that you should use service account impersonation. So the federated token is exchanged once more, through the IAM Credentials API, for an access token of a service account you made for deploys. That works only if the account's own policy grants the role Workload Identity User, the permission to act as it, to the identities you name. The access token lasts one hour by default, which is also the maximum unless an organization policy allows up to 12.

## Three gates

![Three gates between any GitHub workflow and your service: the provider's condition admits one organization ID, the binding on the deployer admits one repository on refs/heads/main, and the deployer's roles cover one service, one image repository and one runtime account; each gate shows the error a refused run gets](https://exodus.elim.io/media/v2026-09-16/t/exodus/assets/image-151627c39608c5b57bab144755c7c406b5edfe4c8b38cbc17fea0502de6de18a-680x520-svg/bytes "Each gate narrows who gets in. The errors are the ones the auth action and Google return at that gate.")

Three gates:

1. **The provider's condition** decides which tokens get into the pool at all. Put your organization here.
1. **The binding on the deployer account** decides which of those may become `deployer`. Put the repository and the branch here.
1. **The deployer's roles** decide what it can touch once it is `deployer`. Put single resources here, not the project.

Leaving gate 1 open is the dangerous mistake: a later grant to "the whole pool" would admit all of GitHub.

## Step 1: the APIs, the project number and the IDs

Workload Identity Federation needs three APIs: IAM, the Security Token Service and the IAM Service Account Credentials API.

```shell
PROJECT=shop-prod-7f3a
gcloud services enable iam.googleapis.com sts.googleapis.com \
  iamcredentials.googleapis.com --project=$PROJECT
gcloud projects describe $PROJECT --format='value(projectNumber)'
```

The provider's full name and every principal you grant use the project **number**, not the ID; the auth action refuses an ID. The examples below use `123456789012`.

Then find the numeric IDs of your GitHub organization and repository. The GitHub CLI prints them:

```shell
gh api orgs/YOUR-ORG --jq .id
gh api repos/YOUR-ORG/YOUR-REPO --jq .id
```

For a personal account, `gh api users/YOUR-NAME --jq .id` gives the owner ID. Why IDs and not names: Google's page warns that if you delete a repository or an organization, someone else may register the same name and get the same identity, while `repository_id` and `repository_owner_id` are never reused. The examples use the IDs from Google's own page, `1342004` for the owner and `20300177` for the repository.

## Step 2: a deployer account with no keys

```shell
gcloud iam service-accounts create deployer \
  --display-name="GitHub deploys" --project=$PROJECT
```

Keep it separate from the account the service runs as (`app-runtime` here; [IAM for a Cloud Run app](https://exodus.elim.io/blog/iam-for-a-cloud-run-app) sets both up): one reads secrets and the database, the other changes the service. Each application gets its own deployer, as Google's best practices say.

## Step 3: the pool and the GitHub provider

```shell
gcloud iam workload-identity-pools create github --location=global \
  --display-name="GitHub Actions" --project=$PROJECT

gcloud iam workload-identity-pools providers create-oidc github \
  --location=global --workload-identity-pool=github --project=$PROJECT \
  --issuer-uri="https://token.actions.githubusercontent.com" \
  --attribute-mapping="google.subject=assertion.sub,attribute.repo_ref=assertion.repository_id + ':' + assertion.ref" \
  --attribute-condition="assertion.repository_owner_id == '1342004'"
```

Line by line:

- **The subject.** `google.subject=assertion.sub` is the mapping Google asks for at minimum. Audit logs show the subject, such as `repo:your-org/your-repo:ref:refs/heads/main`, as the caller.
- **The repo_ref attribute.** `attribute.repo_ref` holds the repository's ID and the Git ref, such as `20300177:refs/heads/main`. Gate 2 grants on it. Only mapped claims can be used in conditions and grants.
- **The condition.** Only tokens whose owner is your organization's ID get in. Google's own example uses the organization's name, `assertion.repository_owner == 'ORGANIZATION'`, and the same page warns about names; the ID is the safer of the two.

Pool and provider IDs cannot be changed later, and the prefix `gcp-` is reserved. Changes to pools, providers and IAM can take up to 5 minutes to apply (Google and the auth action both say so), so a test right after this step may fail and then work.

Why not grant on `google.subject` instead? Because GitHub is changing it. Repositories created after July 15, 2026 get an immutable subject that includes the IDs, such as `repo:octo-org@123456/octo-repo@456789:ref:refs/heads/main`, older repositories keep the old format unless someone opts in, and a rename or transfer after that date switches to the new one. A grant on the subject breaks the day the format changes; a grant on `repo_ref` does not. Use a job's `environment` and the subject changes again (`repo:org/repo:environment:production`), while `ref` stays.

## Step 4: let one repository's main branch become the deployer

```shell
gcloud iam service-accounts add-iam-policy-binding \
  deployer@$PROJECT.iam.gserviceaccount.com --project=$PROJECT \
  --role=roles/iam.workloadIdentityUser \
  --member="principalSet://iam.googleapis.com/projects/123456789012/locations/global/workloadIdentityPools/github/attribute.repo_ref/20300177:refs/heads/main"
```

The `principalSet://` member means "every identity in this pool whose `repo_ref` is this value". Pull requests run with a ref like `refs/pull/42/merge`, so they pass gate 1 and stop here: unmerged code cannot change production, though it can still run tests.

Avoid the member that ends in `/*`, which means the whole pool. Google's best practices name it explicitly ("avoid granting access to all members of a pool"), because then gate 1 is the only gate.

## Step 5: give the deployer what a deploy needs and nothing else

Google's Cloud Run page lists what a deploy of a container image needs: Cloud Run Developer on the service, read access to the image's Artifact Registry repository, and Service Account User on the account the service runs as. A pipeline that also pushes the image needs Artifact Registry Writer, which reads and writes.

```shell
SA=deployer@$PROJECT.iam.gserviceaccount.com
gcloud run services add-iam-policy-binding web --region=europe-west1 \
  --project=$PROJECT --member=serviceAccount:$SA --role=roles/run.developer
gcloud artifacts repositories add-iam-policy-binding app \
  --location=europe-west1 --project=$PROJECT \
  --member=serviceAccount:$SA --role=roles/artifactregistry.writer
gcloud iam service-accounts add-iam-policy-binding \
  app-runtime@$PROJECT.iam.gserviceaccount.com --project=$PROJECT \
  --member=serviceAccount:$SA --role=roles/iam.serviceAccountUser
```

A role on a single service needs the service to exist, so create it once by hand and let the pipeline take over. Google warns that Service Account User lets the holder act with all the roles of that account. If `app-runtime` has the Editor role, so does your pipeline. Keep the runtime account narrow, as the guide on [secrets on Cloud Run](https://exodus.elim.io/blog/secrets-on-cloud-run) describes.

Deploying from source with `gcloud run deploy --source` needs broader roles on the project (Cloud Run Source Developer and Service Usage Consumer), which is one reason to build the image in the workflow, from a Dockerfile or with [buildpacks](https://exodus.elim.io/blog/buildpacks-or-a-dockerfile).

## Step 6: the workflow

```yaml
name: deploy
on:
  push:
    branches: [main]
permissions:
  contents: read
  id-token: write
concurrency:
  group: deploy-production
  cancel-in-progress: false
jobs:
  deploy:
    runs-on: ubuntu-latest
    env:
      IMAGE: europe-west1-docker.pkg.dev/shop-prod-7f3a/app/web:${{ github.sha }}
    steps:
      - uses: actions/checkout@v7
      - run: docker build -t "$IMAGE" .
      - uses: google-github-actions/auth@v3
        with:
          workload_identity_provider: projects/123456789012/locations/global/workloadIdentityPools/github/providers/github
          service_account: deployer@shop-prod-7f3a.iam.gserviceaccount.com
      - uses: google-github-actions/setup-gcloud@v3
      - run: gcloud auth configure-docker europe-west1-docker.pkg.dev --quiet
      - run: docker push "$IMAGE"
      - run: |
          gcloud run deploy web --image "$IMAGE" \
            --region europe-west1 --project shop-prod-7f3a \
            --service-account app-runtime@shop-prod-7f3a.iam.gserviceaccount.com
```

What each part is for:

- **The id-token permission** (`id-token: write`) lets the job ask GitHub for the token. The auth action's README warns that a `permissions` block may remove some default permissions, so `contents: read` is listed for the checkout.
- **The build comes before the sign-in.** The auth action writes a credentials file, `gha-creds-*.json`, into the workspace, and its troubleshooting page warns that a build or a commit after that step can pick it up. Building first keeps it out of the image. If you must build after signing in, add `gha-creds-*.json` to `.gitignore` and `.dockerignore`.
- **The provider input** (`workload_identity_provider`) is the provider's full name with the project number, not the pool's. `service_account` makes the action impersonate `deployer`; without it the action tries direct federation, which Cloud Run does not accept.
- **The image is tagged with the commit**, so each revision points at its code, and a [rollback](https://exodus.elim.io/blog/revisions-traffic-splitting-and-rollbacks) can find the image it needs.
- **The concurrency group** makes two quick pushes deploy in turn instead of racing.

The versions are the current releases on October 12, 2026, and `actionlint` finds no errors in the file. `google-github-actions/deploy-cloudrun@v3` can replace the last step; the READMEs say these actions are not an officially supported Google product.

The access token lasts an hour, so a job longer than that should push in one job and deploy in another, each signing in for itself.

## Step 7: check it

Run the workflow from `main`, and once from another branch with `workflow_dispatch`. The first should deploy; the second should fail at sign-in, because gate 2 refuses it. If both fail, see "What breaks".

Every change the deployer makes is in the Admin Activity audit log, which is always on, with the GitHub subject behind it:

```shell
gcloud logging read \
  'protoPayload.authenticationInfo.principalEmail="deployer@shop-prod-7f3a.iam.gserviceaccount.com"' \
  --project=shop-prod-7f3a --freshness=7d --limit=5 \
  --format='value(timestamp,protoPayload.methodName,protoPayload.authenticationInfo.serviceAccountDelegationInfo[0].principalSubject)'
```

To log every token exchange as well, Google's best practices say to turn on Data Access logs for the Security Token Service and IAM APIs; they are off by default and billed as log volume.

And confirm there is no key to leak:

```shell
gcloud iam service-accounts keys list --managed-by=user \
  --iam-account=deployer@shop-prod-7f3a.iam.gserviceaccount.com
```

Exodus team: our own Cloud Run services deploy this way, from GitHub Actions on a self-hosted runner, a machine we run jobs on instead of GitHub's. Here is the read-only view of one project, with the IDs and names masked:

![A terminal with four read-only commands: the provider's condition on a masked organization ID and its mappings, the deployer's policy granting Workload Identity User to four masked repository IDs on refs/heads/main, a keys list with 0 items, and an audit log entry of October 7, 2026 naming the repository and branch behind a deploy](https://exodus.elim.io/media/v2026-09-16/t/exodus/assets/image-b929d9c6be3976431867be4a1a8c9442733e6f7ac3f890a6b18b4fb78d6209ef-680x596-svg/bytes "Our provider, deployer and audit log, read-only, Oct 12, 2026. IDs and names are masked.")

One organization by ID, four repositories pinned to `refs/heads/main`, and no role on the project: Cloud Run Developer on 4 single services, Artifact Registry Writer on 4 single repositories, and Service Account User on 4 runtime accounts. It has no user-managed keys. On October 7, 2026 it made 26 service updates, and each log entry names the repository and branch that did it.

One lesson: we ask the auth action for a plain access token (`token_format: access_token`, `create_credentials_file: false`) and pass it to gcloud as `CLOUDSDK_AUTH_ACCESS_TOKEN`. With the credentials file, gcloud went back to GitHub for a fresh token when it refreshed, and on our runner's network that call once stalled past gcloud's 120-second timeout.

## What is in the credentials file

The file the auth action writes is a credential configuration: where to get the GitHub token and where to exchange it. A local command makes one, so you can see its shape:

![A terminal: a credential configuration made with gcloud holds only addresses (the provider, Google's token service, the token source and the impersonation URL), and two token exchanges with a made-up token are refused with invalid_target and invalid_grant](https://exodus.elim.io/media/v2026-09-16/t/exodus/assets/image-5774eae7a644332ce8068d10b358699ad3e0ce48c7ef0df0746f07df126e1e38-680x536-svg/bytes "A credential configuration holds addresses, not a secret, and Google refuses a token that does not match. Real output, Oct 12, 2026.")

There is no key in it. During the run it points at GitHub's token endpoint for that job, which is why it must not end up in an image; the action deletes it when the job ends. A token that does not match is refused: our made-up token got `invalid_target` from a provider that does not exist and `invalid_grant` ("The audience in ID Token ... does not match the expected audience") from a real one.

## The other keyless way: Cloud Build triggers

Cloud Run can also deploy continuously through a Cloud Build trigger connected to the repository. The build runs inside Google as a service account, so there is no key and no federation. The build's account needs Cloud Build Service Account, Cloud Run Admin and Service Account User, which is broader than the deployer above. For a team that already tests in GitHub Actions, federation keeps it in one place.

## What it costs

List prices on October 12, 2026:

- **Workload Identity Federation and IAM:** free. Google's IAM pricing page says all use of the IAM API is free of charge.
- **GitHub Actions:** free for public repositories on standard runners and for self-hosted runners. Private repositories get 2,000 minutes a month on GitHub Free and 3,000 on Pro and Team, then $0.006 a minute on a 2-core Linux runner.
- **Artifact Registry:** 0.5 GiB-month free per billing account, then $0.000136986 per GiB-hour, which is about $0.10 per GiB-month. Pulls by Cloud Run in the same region are free.
- **Cloud Build, if you use triggers instead:** 2,500 free build-minutes a month per billing account on `e2-standard-2`, then $0.006 a minute.

A worked example. A private repository that deploys 30 times a month with a 6-minute job uses 180 minutes, inside GitHub's free 2,000, so the pipeline costs $0. The registry is the part that grows: every pushed image stays until it is deleted, so 30 pushes of a 150 MiB image add up to 4.4 GiB a month (less where images share layers), about $0.39 a month above the free 0.5 GiB, and more each month after. Most of it is old images, so add a cleanup policy that keeps the last few.

## What breaks

- **"Failed to generate Google Cloud federated token".** The token did not pass gate 1. The auth action's troubleshooting page says to check the condition first. Common causes: the condition uses a claim you did not map, a typo in the ID, or a name with the wrong letter case (GitHub ignores case, Google does not).
- **"Failed to generate OAuth 2.0 Access Token".** In our runs, a 403 on `iam.serviceAccounts.getAccessToken`. Gate 2: the binding on the deployer does not match this run. Expected for branches and pull requests; for `main`, check the repository ID and the `repo_ref` value.
- **The provider path is wrong.** It must end in `/providers/NAME` and start with the project number. A pool path or a project ID fails.
- **No id-token permission.** Without `id-token: write` the job cannot request a token at all. If a job has its own `permissions` block, put it there too.
- **"The size of mapped attribute exceeds the 127 bytes limit".** Google's limit for a mapped subject is 127 bytes. A long organization name, repository name and branch can pass it; the troubleshooting page says shorter names are the only fix, or map `google.subject` to something shorter.
- **The deploy fails on actAs.** A permission error on `iam.serviceAccounts.actAs` is gate 3: the deployer lacks Service Account User on the runtime account named in `--service-account`.
- **A grant on the subject stopped matching.** The repository was renamed, transferred or opted into GitHub's immutable subject. Grant on the IDs instead.
- **The credentials file is in the image.** The build ran after the auth step. Move the build before it, or add `gha-creds-*.json` to `.dockerignore`.
- **A long job fails after an hour.** The access token expired. Split the job, or raise `access_token_lifetime` where the organization policy allows it.

## Checklist

1. A `deployer` service account per application, with no user-managed keys.
1. A pool and a GitHub provider with a condition on `repository_owner_id`.
1. A custom attribute with the repository ID and the ref, and the Workload Identity User grant on it, for `refs/heads/main` only.
1. No grant to the whole pool.
1. Cloud Run Developer on the service, Artifact Registry Writer on the repository, Service Account User on the runtime account; nothing on the project.
1. A runtime account without Editor, because the deployer can act as it.
1. `id-token: write` in the workflow, the build before the auth step, images tagged with the commit.
1. A run from another branch fails at sign-in.
1. The audit log shows the repository and branch for each deploy.

## Where Exodus comes in

The Exodus CLI's audit notes whether the repository has a CI workflow under `.github/workflows`, and the move plan in its report has a step "Keyless deploys from CI": GitHub Actions signs in with Workload Identity Federation, with no key file anywhere, before [the cutover day](https://exodus.elim.io/blog/the-cutover-day-checklist), so the new app deploys from CI at its `run.app` address. Exodus itself never signs in to Google Cloud; your coding agent runs the gcloud commands above with your own gcloud, and IAM changes wait for your yes. The same workflow can run `exodus check`, which fails the build when a pattern that breaks on Cloud Run comes back.

## FAQ

### What is Workload Identity Federation in Google Cloud?

Workload Identity Federation lets a program outside Google Cloud, such as a GitHub Actions run, sign in with a token from its own platform instead of a service account key. Google's Security Token Service checks the token against a pool, a provider and its condition, and returns a short-lived federated token.

### Why does google-github-actions/auth fail with "Failed to generate Google Cloud federated token"?

The token did not pass the provider's attribute condition. Check the condition first: it may use a claim that is not mapped, have a typo in the organization ID, or compare a name in the wrong letter case, since GitHub ignores case and Google does not.

### Can a pull request deploy to Cloud Run with this setup?

No. A pull request runs with a ref like refs/pull/42/merge, so its token passes the provider's condition but not the binding on the deployer, which admits only refs/heads/main. Unmerged code cannot change production, though the same workflow can still run its tests.

### Can Cloud Run be deployed with a federated token directly, without a service account?

No. Google's list of products and limitations says Cloud Run does not support direct resource access by federated identities. The federated token is exchanged once more, through the IAM Credentials API, for a one-hour token of the deployer service account, which is what the auth action's service_account input does.

## Sources

1. [Configure Workload Identity Federation with deployment pipelines, IAM, Google (GitHub OIDC tokens identify the workflow and repository; map google.subject to assertion.sub; repository, repository_id, repository_owner, repository_owner_id; use the numeric *_id fields because names can be claimed again; you must use an attribute condition for GitHub, which shares one issuer across all organizations; issuer https://token.actions.githubusercontent.com/; the IAM, Security Token Service and Service Account Credentials APIs; principalSet by attribute; Workload Identity User for impersonation; project number, not ID, in the member; prefix gcp- reserved; updated Oct 8, 2026, read Oct 12, 2026)](https://docs.cloud.google.com/iam/docs/workload-identity-federation-with-deployment-pipelines)
2. [Best practices for using Workload Identity Federation, IAM, Google (attribute conditions for GitHub and other multi-tenant providers; immutable and non-reusable attributes; the provider URL as the audience; Data Access logs for the Security Token Service and IAM APIs; serviceAccountDelegationInfo carries the subject; a dedicated service account for each application; updated Oct 9, 2026, read Oct 12, 2026)](https://docs.cloud.google.com/iam/docs/best-practices-for-using-workload-identity-federation)
3. [Workload Identity Federation overview, IAM, Google (Security Token Service exchange, federated token, attribute conditions; updated Oct 7, 2026, read Oct 12, 2026)](https://docs.cloud.google.com/iam/docs/workload-identity-federation)
4. [Identity federation: products and limitations, IAM, Google ('Cloud Run doesn't support Workload Identity Federation direct resource access. To allow access, use service account impersonation'; Artifact Registry and Cloud Build without known limitations; updated Oct 9, 2026, read Oct 12, 2026)](https://docs.cloud.google.com/iam/docs/federated-identity-supported-services)
5. [IAM quotas and limits, Google (mapped subject 127 bytes; 50 custom attribute mappings; 6,000 global token exchange requests a minute per project; 60,000 credential requests a minute per project; updated Oct 7, 2026, read Oct 12, 2026)](https://docs.cloud.google.com/iam/quotas)
6. [IAM pricing, Google ('All use of Identity and Access Management API is free of charge'; read Oct 12, 2026)](https://cloud.google.com/iam/pricing)
7. [IAM billing questions, Google (IAM is offered at no additional charge; updated Oct 9, 2026, read Oct 12, 2026)](https://docs.cloud.google.com/iam/docs/billing-questions)
8. [Best practices for managing service account keys, IAM, Google (keys are not subject to 2-step verification; keys created in IAM have no expiry time by default; do not commit keys; enable GitHub secret scanning; updated Oct 7, 2026, read Oct 12, 2026)](https://docs.cloud.google.com/iam/docs/best-practices-for-managing-service-account-keys)
9. [Secure by default organizations, Resource Manager, Google (service account key creation and key upload disabled for organizations created on or after May 3, 2024; updated Oct 9, 2026, read Oct 12, 2026)](https://docs.cloud.google.com/resource-manager/docs/secure-by-default-organizations)
10. [Cloud Run IAM roles, deployment permissions, Google (Cloud Run Developer on the resource, Artifact Registry Reader on the repository, Service Account User on the service identity; Service Account User lets the deployer act as that account; Cloud Run Source Developer and Cloud Run Builder for source deploys; updated Oct 8, 2026, read Oct 12, 2026)](https://docs.cloud.google.com/run/docs/reference/iam/roles)
11. [Artifact Registry access control, Google (Artifact Registry Writer: read and write artifacts; updated Oct 9, 2026, read Oct 12, 2026)](https://docs.cloud.google.com/artifact-registry/docs/access-control)
12. [Continuously deploy from a repository, Cloud Run, Google (Cloud Build or Developer Connect triggers from GitHub; roles for the person and for the build's service account; updated Oct 9, 2026, read Oct 12, 2026)](https://docs.cloud.google.com/run/docs/continuous-deployment-with-cloud-build)
13. [Cloud Build pricing, Google (2,500 free build-minutes a month per billing account on e2-standard-2 in the default pool; e2-standard-2 $0.006 a minute; read Oct 12, 2026)](https://cloud.google.com/build/pricing)
14. [Artifact Registry pricing, Google (0.5 GiB-month free per billing account, then $0.000136986 per GiB-hour, about $0.10 per GiB-month; transfer to Cloud Run in the same continent free; read Oct 12, 2026)](https://cloud.google.com/artifact-registry/pricing)
15. [google-github-actions/auth README, v3 (direct federation, federation through a service account, or a key; id-token: write; checkout before auth; gha-creds-*.json in the workspace; the GitHub OIDC token expires in 5 minutes; access_token_lifetime default and maximum 1 hour, 12 hours with the lifetime extension policy; up to 5 minutes for pool, provider and IAM changes to propagate; not an officially supported Google product; read Oct 12, 2026)](https://github.com/google-github-actions/auth)
16. [google-github-actions/auth troubleshooting (project number not ID; the provider name, not the pool; 'Failed to generate Google Cloud federated token' means the attribute condition, 'Failed to generate OAuth 2.0 Access Token' means impersonation; Google is case-sensitive where GitHub is not; the 127-byte limit; a credentials file inside a build; read Oct 12, 2026)](https://github.com/google-github-actions/auth/blob/main/docs/TROUBLESHOOTING.md)
17. [google-github-actions/setup-gcloud README, v3 (read Oct 12, 2026)](https://github.com/google-github-actions/setup-gcloud)
18. [google-github-actions/deploy-cloudrun README, v3 (deploys an image or source; read Oct 12, 2026)](https://github.com/google-github-actions/deploy-cloudrun)
19. [OpenID Connect reference, GitHub Docs (claims: sub, ref, repository_id, repository_owner_id, job_workflow_ref, environment; sub formats for branches, tags, environments and pull requests; repositories created after July 15, 2026 use an immutable sub with owner and repository IDs; id-token: write only allows fetching the token; read Oct 12, 2026)](https://docs.github.com/en/actions/reference/security/oidc)
20. [Configuring OpenID Connect in Google Cloud Platform, GitHub Docs (read Oct 12, 2026)](https://docs.github.com/en/actions/how-tos/secure-your-work/security-harden-deployments/oidc-in-google-cloud-platform)
21. [GitHub Actions billing, GitHub Docs (free for public repositories on standard runners and for self-hosted runners; 2,000 minutes a month on GitHub Free, 3,000 on Pro and Team; Linux 2-core $0.006 a minute; read Oct 12, 2026)](https://docs.github.com/en/billing/concepts/product-billing/github-actions)
22. Exodus team: our GitHub provider, deployer account and audit log entries, read-only, Oct 12, 2026; token exchanges with a made-up token refused by Google's Security Token Service, Oct 12, 2026
