How do I deploy from GitHub Actions to the cloud without long lived secrets?
Use OpenID Connect (OIDC). Your cloud provider trusts GitHub as an identity provider, each job asks GitHub for a signed token, and the cloud swaps it for credentials that expire with the job. You add id-token: write to the workflow and lock the cloud role to your repository and branch or environment.
Cite
GitShowcase Forum, "How do I deploy from GitHub Actions to the cloud without long lived secrets?", https://gitshowcase.com/forum/discuss/how-do-i-deploy-from-github-actions-to-the-cloud-without-long-lived/, accessed 2026-11-15.- Maren CastilloStaffForum lead: databases, hosting, CI and production ·
Why this replaces stored keys
The old pattern is to create an access key in your cloud account, paste it into a GitHub secret, and present it on every run. That key lives until someone rotates it, and anyone who can read the secret can use it from anywhere. According to the GitHub Actions OIDC docs, OIDC removes the duplicated cloud secret: you set up a trust relationship once, and the provider issues a short-lived token that is only valid for a single job and then expires on its own.
The flow, in four steps:
- You tell the cloud that tokens from GitHub's issuer are acceptable, and for which workflows.
- Each job can request a JSON web token (JWT) from GitHub. It carries claims such as the repository, the branch or environment, and the workflow.
- The workflow presents that token to the cloud.
- The cloud checks the claims against the trust rules you wrote and returns temporary credentials.
Check first
- Is the job running on a GitHub-hosted or self-hosted runner that can reach both GitHub's token endpoint and your cloud's token service?
- Is the job triggered by a pull request? Its token claims differ from a push to
main, so a trust policy written formainwill not match. - Which cloud? The steps below use AWS because GitHub documents it step by step. Azure, Google Cloud, HashiCorp Vault and others have their own pages in the same docs section, and the shape is the same.
Step by step on AWS
1. Add GitHub as an identity provider in IAM. Per the GitHub guide for AWS, the provider URL is
https://token.actions.githubusercontent.com, and the audience issts.amazonaws.comif you use the officialaws-actions/configure-aws-credentialsaction.2. Create a role with a trust policy that names your repo. The docs say you must define at least one condition so that untrusted repositories cannot request tokens for your resources. Match on the
subclaim:{ "Effect": "Allow", "Principal": { "Federated": "arn:aws:iam::<ACCOUNT_ID>:oidc-provider/token.actions.githubusercontent.com" }, "Action": "sts:AssumeRoleWithWebIdentity", "Condition": { "StringEquals": { "token.actions.githubusercontent.com:aud": "sts.amazonaws.com", "token.actions.githubusercontent.com:sub": "repo:<OWNER>/<REPO>:environment:prod" } } }3. Give the role only the permissions the deploy needs. A role that can write one bucket or update one service is far safer than an admin role. The trust policy decides who can get in; the permission policy decides what they can do.
4. Update the workflow. Two changes: the
permissionsblock and the credentials step.name: deploy on: push: branches: [main] permissions: id-token: write # lets the job request the OIDC token contents: read # lets checkout read the repo jobs: deploy: runs-on: ubuntu-latest environment: prod steps: - uses: actions/checkout@v6 - uses: aws-actions/configure-aws-credentials@<PINNED_COMMIT_SHA> with: role-to-assume: arn:aws:iam::<ACCOUNT_ID>:role/<DEPLOY_ROLE> aws-region: <REGION> - run: aws s3 sync ./dist s3://<BUCKET_NAME>/Pin third party actions to a full commit SHA where you can, as GitHub's own example does.
id-token: writedoes not let the workflow write to your repo; the docs say it only allows requesting and using the OIDC token.Match the
subclaim you actually haveMost failures are a mismatch between the token's
suband the trust policy. The common shapes, as of the GitHub docs in November 2026:Workflow context sublooks likeBranch push repo:<OWNER>/<REPO>:ref:refs/heads/<BRANCH>Job with an environment repo:<OWNER>/<REPO>:environment:<ENV>Any branch or environment in one repo StringLikewithrepo:<OWNER>/<REPO>:*The docs also note a newer format: repositories created after July 15, 2026, or that opt in, include immutable owner and repository IDs in
sub, written likerepo:<OWNER>@<ID>/<REPO>@<ID>:.... If your role worked for old repos and fails for a new one, print the claim and compare. Also remember that when a job uses an environment,subnames the environment, not the branch.Add protection on the environment
When you key trust to an environment, add protection rules on that environment in GitHub, such as restricting which branches can deploy to it. GitHub's AWS page recommends this for extra security. Without it, anyone who can push a workflow that targets the environment can request the same token.
Common errors
- "Credentials could not be loaded" or "Not authorized to perform sts:AssumeRoleWithWebIdentity": the
suboraudin the trust policy does not match the token. Check both. - "Unable to get ACTIONS_ID_TOKEN_REQUEST_URL": the job is missing
id-token: write. Set it at the workflow or job level. - Works on
main, fails on a pull request: your trust policy names a branch or environment the PR job does not have.
If your cache step is also misbehaving in the same workflow, see why a GitHub Actions cache never restores. For platform comparisons, the CI/CD guide covers the options.
Write something first.
Give people something to work with: at least 30 words on what happened and what you tried.
That is too long. Keep it under 6,000 characters.
Write the question as the title, 15 to 140 characters, no links.
Pick a category.
Add a name (2 to 40 characters, no links).
That email address does not look right.
That was quick. Read the thread, then try again.
The form expired. Reload the page and post again.
Something went wrong with the form. Reload and try again.
Please complete the check and post again.
Limit reached for now. Try again later.
This thread is closed to new replies.
This thread no longer accepts replies.
Something went wrong with the form. Reload and try again.
Post a reply or question first (name and email), then this browser can vote, edit and accept answers.
You cannot vote on your own post.
Only the author (within 30 days) or the forum team can do that.
That email already has an account. Use your sign-in link instead.
New accounts are paused for the moment. Try again later.
That was already posted.