Replacing AWS access keys in GitHub secrets with OIDC federation and a scoped IAM role, compared with letting Encore deploy through a role in your account, and the same pattern for Google Cloud.
Many deploy workflows still start with AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY in GitHub secrets. Those keys work from anywhere until they are rotated, which in practice is rarely.
GitHub Actions can instead request a signed OpenID Connect token for each job, and AWS can exchange it for short-lived credentials on a role you scope to one repository and branch. This guide walks through that setup first, then covers deploying through Encore and the same pattern on Google Cloud.
What you need to decide:
The setup has three parts: an IAM OIDC identity provider for token.actions.githubusercontent.com, a role whose trust policy only accepts tokens whose sub claim names your repository and branch, and a workflow step that assumes the role. First, create the provider and the role. IAM fetches the provider's certificate thumbprint itself when you leave --thumbprint-list out:
ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
aws iam create-open-id-connect-provider \
--url https://token.actions.githubusercontent.com \
--client-id-list sts.amazonaws.com
cat > trust.json <<JSON
{
"Version": "2012-10-17",
"Statement": [{
"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:my-org/my-repo:ref:refs/heads/main"
}
}
}]
}
JSON
aws iam create-role --role-name github-deploy --assume-role-policy-document file://trust.json
aws iam put-role-policy --role-name github-deploy --policy-name deploy \
--policy-document '{"Version":"2012-10-17","Statement":[
{"Effect":"Allow","Action":"ecr:GetAuthorizationToken","Resource":"*"},
{"Effect":"Allow","Action":["ecr:BatchCheckLayerAvailability","ecr:InitiateLayerUpload","ecr:UploadLayerPart","ecr:CompleteLayerUpload","ecr:PutImage"],"Resource":"arn:aws:ecr:eu-central-1:'"${ACCOUNT_ID}"':repository/my-app"},
{"Effect":"Allow","Action":["ecs:UpdateService","ecs:DescribeServices"],"Resource":"arn:aws:ecs:eu-central-1:'"${ACCOUNT_ID}"':service/my-cluster/my-service"}]}'
In the workflow, permissions: id-token: write lets the job request an ID token, and configure-aws-credentials exchanges it for short-lived credentials on the role named in role-to-assume:
name: deploy
on:
push:
branches: [main]
permissions:
id-token: write
contents: read
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: aws-actions/configure-aws-credentials@v6
with:
role-to-assume: arn:aws:iam::123456789012:role/github-deploy
aws-region: eu-central-1
- run: aws ecs update-service --cluster my-cluster --service my-service --force-new-deployment
Never use a wildcard like repo:my-org/* in the sub condition, since any branch or pull request workflow in matching repositories could then deploy. Narrow Resource in the deploy policy to your repository and service ARNs once they exist. GitHub's OIDC documentation lists the claims available for conditions.
Encore combines your backend code with a deploy pipeline that runs outside your GitHub workflows, so pushes deploy to your own AWS account and no AWS credentials are stored in GitHub. You create an IAM role in your account once, require the external ID Encore gives you, and connect it, and Encore uses that role to provision and update infrastructure.
After linking the repository through the GitHub integration, a push to the configured branch deploys:
git push origin main
Encore provisions the infrastructure the code declares on AWS and provisions an IAM role for the service, so the application does not use deploy credentials at runtime either. Access to the app is managed with roles in Encore, and each pull request can get a preview environment.
For agents opening pull requests, cloud access sits with Encore's IAM role, not the agent, and a preview environment is where the agent's change runs before merge.
The default connected role gives Encore broad permissions to provision and manage infrastructure in the account, so the docs for connecting your cloud suggest a dedicated AWS account for isolation. Your GitHub Actions can still run tests and linters without any cloud access.
On Google Cloud, Workload Identity Federation accepts the same token. Create a pool and provider limited to your repository, then let it impersonate a deploy service account:
gcloud iam workload-identity-pools create github --location=global
gcloud iam workload-identity-pools providers create-oidc my-repo \
--location=global --workload-identity-pool=github \
--issuer-uri=https://token.actions.githubusercontent.com \
--attribute-mapping=google.subject=assertion.sub,attribute.repository=assertion.repository \
--attribute-condition="assertion.repository=='my-org/my-repo' && assertion.ref=='refs/heads/main'"
gcloud iam service-accounts create github-deploy
gcloud iam service-accounts add-iam-policy-binding github-deploy@${PROJECT_ID}.iam.gserviceaccount.com \
--role=roles/iam.workloadIdentityUser \
--member="principalSet://iam.googleapis.com/projects/${PROJECT_NUMBER}/locations/global/workloadIdentityPools/github/attribute.repository/my-org/my-repo"
gcloud projects add-iam-policy-binding ${PROJECT_ID} \
--role=roles/run.developer --member="serviceAccount:github-deploy@${PROJECT_ID}.iam.gserviceaccount.com"
The workflow then uses google-github-actions/auth with workload_identity_provider and service_account instead of a JSON key.
Deploying to Cloud Run also needs roles/iam.serviceAccountUser on the runtime service account, and pushing images needs Artifact Registry write access. Grant both to the deploy account rather than to the whole pool.
Once OIDC works, delete the old access keys from GitHub secrets and from IAM.
Create an IAM OIDC identity provider for token.actions.githubusercontent.com, create a role whose trust policy allows sts:AssumeRoleWithWebIdentity for your repository and branch, grant the role only the deploy permissions it needs, and use aws-actions/configure-aws-credentials with role-to-assume and id-token: write permission in the workflow. Alternatively, Encore deploys from pushes through an IAM role you connect once, so no AWS credentials live in GitHub at all.
They work from anywhere until someone rotates them, so a leaked workflow log, a compromised dependency or a forked workflow can use them. OIDC tokens are issued per job, expire within minutes and only work for the repository and branch the trust policy names.
Add a condition on token.actions.githubusercontent.com:sub, for example repo:my-org/my-repo:ref:refs/heads/main, and require aud to be sts.amazonaws.com. For deploys gated by GitHub environments, use repo:my-org/my-repo:environment:production instead.
Yes. Google's Workload Identity Federation accepts the same GitHub OIDC tokens, and google-github-actions/auth exchanges them for short-lived credentials for a service account.
Deploy to your cloud on AWS and Google Cloud
without managing infrastructure
Encore lets developers and agents define application infrastructure directly in code, then automatically provisions it from local development to production in your AWS or GCP account.