AWS Builder Center

Continuous Deployment to AWS with GitHub Actions and OIDC: Say Goodbye to Long-Lived Credentials

Build a secure, least-privilege CI/CD pipeline using OpenID Connect (OIDC), Amazon ECR, and AWS App Runner. Eliminate long-lived IAM access keys and automate building, pushing, and deploying your containerized full-stack application in just a few steps.

Traditional CI/CD pipelines targeting AWS often relied on long-lived IAM user access keys (AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY) stored as repository secrets. Beyond violating security best practices, accidental exposure of these static credentials puts your entire cloud environment at risk.
In this tutorial, we implement a Zero Long-Lived Credentials pattern using GitHub's OpenID Connect (OIDC) provider paired with AWS IAM Roles.

What You Will Build

  1. An OIDC identity provider configuration in IAM with fine-grained trust conditions.
  1. A private Amazon ECR container registry to host versioned images.
  1. An AWS App Runner managed service to execute containerized apps without managing servers, VPCs, or load balancers.
  1. A production-ready GitHub Actions workflow in YAML.

Prerequisites

  • An active AWS account with permissions for IAM, ECR, and App Runner.
  • A GitHub repository containing your application code and a functional  Dockerfile .
  • The AWS CLI installed and configured locally for initial setup.
  1. Configure the OIDC Provider and IAM Role GitHub Actions provides an OIDC endpoint (token.actions.githubusercontent.com). We register this endpoint with AWS IAM and create a role that only your specific repository can assume.
1.1 Register the GitHub OIDC Provider Run the following AWS CLI command if your AWS account does not already have GitHub registered as an identity provider:
Bash
aws iam create-open-id-connect-provider \
--url https://token.actions.githubusercontent.com \
--client-id-list sts.amazonaws.com \
--thumbprint-list 6938fd4d98bab03faadb97b34396831e3780aea1 \
--thumbprint-list 1c58a3a8518e8759bf075b76b750d4f2df264fcd
1.2 Create the Trust Policy Document (trust-policy.json) Replace <YOUR_ACCOUNT_ID>, <YOUR_GITHUB_ORG_OR_USER>, and <YOUR_REPOSITORY> with your actual details. The StringLike condition restricts access exclusively to your repository:
JSON
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::<YOUR_ACCOUNT_ID>:oidc-provider/token.actions.githubusercontent.com"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
},
"StringLike": {
"token.actions.githubusercontent.com:sub": "repo:<YOUR_GITHUB_ORG_OR_USER>/<YOUR_REPOSITORY>:*"
}
}
}
]
}
Create the IAM Role:
Bash
aws iam create-role \
--role-name GitHubActionsAppRunnerDeployRole \
--assume-role-policy-document file://trust-policy.json

1.3 Attach Least-Privilege Permissions ( permissions-policy.json )

The deployment workflow only requires permissions to authenticate with ECR, upload container layers, and trigger the App Runner deployment:
JSON
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "ECRAuthAccess",
"Effect": "Allow",
"Action": "ecr:GetAuthorizationToken",
"Resource": "*"
},
{
"Sid": "ECRPushAccess",
"Effect": "Allow",
"Action": [
"ecr:CompleteLayerUpload",
"ecr:UploadLayerPart",
"ecr:InitiateLayerUpload",
"ecr:BatchCheckLayerAvailability",
"ecr:PutImage"
],
"Resource": "arn:aws:ecr:*:<YOUR_ACCOUNT_ID>:repository/my-web-app"
},
{
"Sid": "AppRunnerDeployAccess",
"Effect": "Allow",
"Action": [
"apprunner:StartDeployment"
],
"Resource": "*"
}
]
}
Attach the policy inline to the role:
Bash
aws iam put-role-policy \
--role-name GitHubActionsAppRunnerDeployRole \
--policy-name GitHubActionsPermissions \
--policy-document file://permissions-policy.json
  1. Create the Amazon ECR Repository Create a private container registry to store your Docker images:
Bash
aws ecr create-repository \
--repository-name my-web-app \
--image-scanning-configuration scanOnPush=true \
--region us-east-1
Note: Enabling  scanOnPush=true  ensures basic security vulnerability scanning runs automatically whenever a new image tag is uploaded.
  1. Set Up the AWS App Runner Service In the AWS Console, open AWS App Runner and select Create service.
Under Source, choose Container registry > Amazon ECR.
Select the my-web-app repository and pick the latest tag (push an initial placeholder image first if the repository is empty).
Under Deployment trigger, set the mode to Manual.
Configure the runtime settings:
Service name: web-app-production
Port: The internal port your container listens on (e.g., 3000 or 8080).
Environment variables: Add any required application variables (e.g., NODE_ENV=production).
Click through to create the service and record the resulting Service ARN.
  1. Write the GitHub Actions Workflow In your repository, create the workflow file at .github/workflows/deploy.yml.
Important: The directives permissions: id-token: write and contents: read are mandatory. Without id-token: write, GitHub will not issue the OIDC JWT token.
YAML
name: Build and Deploy to AWS
on:
push:
branches:
- main
permissions:
id-token: write
contents: read
env:
AWS_REGION: us-east-1
IAM_ROLE_ARN: arn:aws:iam::<YOUR_ACCOUNT_ID>:role/GitHubActionsAppRunnerDeployRole
ECR_REPOSITORY: my-web-app
APP_RUNNER_SERVICE_ARN: arn:aws:apprunner:us-east-1:<YOUR_ACCOUNT_ID>:service/web-app-production/xxxxxxxxx
jobs:
deploy:
name: Build, Push, and Update Service
runs-on: ubuntu-latest
steps:
- name: Checkout Source Code
uses: actions/checkout@v4
- name: Configure AWS Credentials via OIDC
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ env.IAM_ROLE_ARN }}
aws-region: ${{ env.AWS_REGION }}
audience: sts.amazonaws.com
- name: Log in to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v2
- name: Build, Tag, and Push Docker Image
env:
REGISTRY: ${{ steps.login-ecr.outputs.registry }}
IMAGE_TAG: ${{ github.sha }}
run: |
docker build -t $REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG -t $REGISTRY/$ECR_REPOSITORY:latest .
docker push $REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
docker push $REGISTRY/$ECR_REPOSITORY:latest
- name: Trigger AWS App Runner Deployment
run: |
echo "Starting deployment on App Runner..."
OPERATION_ID=$(aws apprunner start-deployment \
--service-arn ${{ env.APP_RUNNER_SERVICE_ARN }} \
--query 'OperationId' \
--output text)
echo "Deployment initiated successfully. Operation ID: $OPERATION_ID"
  1. Verification and Testing Commit and push the configuration changes to your repository's main branch:
Bash
git add .
git commit -m "ci: configure automated oidc deployment to aws"
git push origin main
  • Navigate to the Actions tab in GitHub to watch the workflow build and push your container.
  • In the AWS App Runner console, monitor the Activity tab for your service until the state reaches  Running .
  • Open the default domain URL provided by App Runner in your browser to confirm your application is serving traffic over HTTPS.
Any opinions in this article are those of the individual author and may not reflect the opinions of AWS.
Enjoyed reading this content? Let the author know!

Your likes, comments, shares, and saves help creators reach more builders.

Loading recommendations

Loading article