Deploy Static Web Apps to AWS S3 Using GitHub Actions

This guide will help you securely deploy a static web application (e.g., Next.js, Vite) to an AWS S3 bucket using GitHub Actions and an IAM Role.
Step 1: Create an IAM Role in AWS
Log in to the AWS Management Console and navigate to IAM > Roles.
Click Create Role.
Select Web Identity as the trusted entity type.
Under Identity Provider, choose
token.actions.githubusercontent.com
. If it doesn’t exist:Click Create Provider.
Set Provider Type to OpenID Connect.
Enter
token.actions.githubusercontent.com
as the Provider URL.Set sts.amazonaws.com as the Audience.
Click Save.
Return to the Create Role screen, select
token.actions.githubusercontent.com
, and confirm sts.amazonaws.com as the audience.Add Permissions: Choose a policy like
AdministratorAccess
(for simplicity) or a custom policy with least privilege for production.Set Trust Policy: Ensure the policy matches the following (replace placeholders with your values):
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::<AWS_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:<GITHUB_USERNAME>/<REPO_NAME>:ref:refs/heads/<BRANCH_NAME>"
}
}
}
]
}
- Name the Role (e.g.,
GitHubActionsRole
) and click Create Role.
Step 2: Configure GitHub Repository Secrets
Go to your GitHub repository.
Navigate to Settings > Secrets and Variables > Actions.
Add the following secrets:
AWS_ROLE_ARN
: The ARN of the IAM role (e.g.,arn:aws:iam::<AWS_ACCOUNT_ID>:role/GitHubActionsRole
).AWS_REGION
: Your AWS region (e.g.,us-east-1
).S3_BUCKET
: The name of your S3 bucket (e.g.,my-app-bucket
).
Step 3: Create a GitHub Actions Workflow
In your repository, create a file at
.github/workflows/deploy.yml
.Add the following YAML configuration to automate the deployment to S3:
name: Deploy to S3
on:
push:
branches:
- main
permissions:
id-token: write
contents: read
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v2
with:
role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
aws-region: ${{ secrets.AWS_REGION }}
- name: Verify AWS Role Assumption
run: aws sts get-caller-identity
- name: Install Dependencies
run: npm install
- name: Build Application
run: npm run build
- name: Deploy Next.js to S3
run: |
if [ -d "out" ]; then
echo "✅ Found 'out/' directory (Next.js export). Deploying..."
aws s3 sync out/ s3://${{ secrets.S3_BUCKET }}/ --delete --cache-control "public, max-age=0, must-revalidate"
else
echo "❌ 'out/' directory not found. Build may have failed."
exit 1
fi
- name: Deploy Vite to S3
run: |
if [ -d "dist" ]; then
echo "✅ Found 'dist/' directory (Vite). Deploying..."
aws s3 sync dist/ s3://${{ secrets.S3_BUCKET }}/ --delete --cache-control "public, max-age=0, must-revalidate"
else
echo "❌ 'dist/' directory not found. Build may have failed."
exit 1
fi
Explanation of the Workflow
Trigger: The workflow is triggered when a push is made to the
main
branch.Permissions: Grants access to the GitHub OIDC token and repository contents.
Steps:
Checkout code: Uses
actions/checkout
to pull the latest code.Configure AWS Credentials: Uses
aws-actions/configure-aws-credentials
to authenticate using the IAM role.Verify Role Assumption: Confirms that the AWS role has been assumed successfully.
Install Dependencies: Runs
npm install
to install the necessary packages.Build Application: Executes
npm run build
to generate the production build.Deploy to S3: Syncs the build output (either
out/
for Next.js ordist/
for Vite) to the S3 bucket, ensuring the removal of outdated files and setting proper cache headers.
Notes
Replace placeholders: Ensure that you replace
<AWS_ACCOUNT_ID>
,<GITHUB_USERNAME>
,<REPO_NAME>
, and<BRANCH_NAME>
with your actual values.Least-privilege policy: For production environments, use a least-privilege IAM policy instead of
AdministratorAccess
.S3 static website hosting: If you’re serving the app directly from S3, make sure your S3 bucket is configured for static website hosting
Subscribe to my newsletter
Read articles from Gaurish Naik directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
