Overview
To automate deployments of my Hugo-based blog to AWS, I built a CI/CD pipeline using GitHub Actions and AWS IAM Roles with OIDC (OpenID Connect) for secure, short-lived credentials.
Here's a step-by-step breakdown of the setup:
Step 1: Set Up GitHub Actions to Build Your Hugo Blog
- Create a GitHub Actions workflow in
.github/workflows/deploy.yml.
2. Use the peaceiris/actions-hugo GitHub Action to install Hugo and build the blog, or you can build your own yaml file.
name: Build and Deploy Hugo Blog
on:
push:
branches:
- main
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout repo
uses: actions/checkout@v3
- name: Setup Hugo
uses: peaceiris/actions-hugo@v2
with:
hugo-version: '0.111.3' # or your preferred version
- name: Build Hugo site
run: hugo --minifyStep 2: Set Up OIDC Between GitHub and AWS
- In your development AWS account, create a custom IAM Identity Provider for GitHub:
- Provider type:
OIDC - Provider URL:
https://token.actions.githubusercontent.com - Audience:
sts.amazonaws.com
2. This integration allows GitHub to assume IAM roles without storing long-term AWS credentials.
Step 3: Create a Least-Privilege IAM Policy
Create a policy (e.g., GitHubActions) with permissions restricted to the target S3 bucket:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["s3:GetObject", "s3:PutObject", "s3:ListBucket"],
"Resource": [
"arn:aws:s3:::your-blog-bucket",
"arn:aws:s3:::your-blog-bucket/*"
]
}
]
}Step 4: Create an IAM Role with Web Identity Trust
- Create a new IAM Role with Web Identity as the trusted entity type.
- Trust the GitHub OIDC Identity Provider you just set up.
- Add a trust policy that allows only your specific GitHub repository to assume the role:
{
"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:sub": "repo:your-username/your-repo-name:ref:refs/heads/main"
}
}
}4. Attach the GitHubActions policy to this role.
Step 5: Store the Role ARN in GitHub Secrets
- In your GitHub repository, go to Settings > Secrets and variables > Actions
- Add a new secret:
AWS_ROLE_ARN: with the ARN of your IAM role
Step 6: Update GitHub Actions Workflow to Assume the IAM Role
Add steps to your workflow to assume the role using aws-actions/configure-aws-credentials:
- name: Configure AWS credentials from IAM Role
uses: aws-actions/configure-aws-credentials@v2
with:
role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
aws-region: us-east-1Step 7: Deploy to S3 and Invalidate CloudFront Cache
Add deployment and cache invalidation steps:
- name: Sync public/ to S3
run: aws s3 sync public/ s3://your-blog-bucket --delete
- name: Invalidate CloudFront cache
run: |
aws cloudfront create-invalidation \
--distribution-id YOUR_DISTRIBUTION_ID \
--paths "/*"Step 8: Grant CloudFront Invalidation Permissions
Update your IAM Policy (GitHubActions) to include:
{
"Effect": "Allow",
"Action": "cloudfront:CreateInvalidation",
"Resource": "arn:aws:cloudfront::your-account-id:distribution/YOUR_DISTRIBUTION_ID"
}Done! CI/CD Complete 🎉
Every time a change is pushed to the main branch:
- Hugo builds the blog
- GitHub Actions assumes the AWS role via OIDC
- The
public/folder is synced to S3 - CloudFront cache is invalidated so visitors see the latest content instantly
💡 Key Takeaways
- 🔐 OIDC means no more long-term AWS keys in GitHub
- 📦 Using Hugo with GitHub Actions is lightweight and fast
- 🚀 CloudFront + S3 = a scalable, serverless blog architecture