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
- An OIDC identity provider configuration in IAM with fine-grained trust conditions.
- A private Amazon ECR container registry to host versioned images.
- An AWS App Runner managed service to execute containerized apps without managing servers, VPCs, or load balancers.
- 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.
- 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
- 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.- 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.
- 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 AWSon:push:branches:- mainpermissions:id-token: writecontents: readenv:AWS_REGION: us-east-1IAM_ROLE_ARN: arn:aws:iam::<YOUR_ACCOUNT_ID>:role/GitHubActionsAppRunnerDeployRoleECR_REPOSITORY: my-web-appAPP_RUNNER_SERVICE_ARN: arn:aws:apprunner:us-east-1:<YOUR_ACCOUNT_ID>:service/web-app-production/xxxxxxxxxjobs:deploy:name: Build, Push, and Update Serviceruns-on: ubuntu-lateststeps:- name: Checkout Source Codeuses: actions/checkout@v4- name: Configure AWS Credentials via OIDCuses: aws-actions/configure-aws-credentials@v4with:role-to-assume: ${{ env.IAM_ROLE_ARN }}aws-region: ${{ env.AWS_REGION }}audience: sts.amazonaws.com- name: Log in to Amazon ECRid: login-ecruses: aws-actions/amazon-ecr-login@v2- name: Build, Tag, and Push Docker Imageenv: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_TAGdocker push $REGISTRY/$ECR_REPOSITORY:latest- name: Trigger AWS App Runner Deploymentrun: |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"
- 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.
Enjoyed reading this content? Let the author know!
Your likes, comments, shares, and saves help creators reach more builders.
Loading recommendations
Loading article