How to set up Github Actions to publish to S3 website
The problem
I used to run a hosted linux web server, which was great for stuff like all those weird little scripts and things I wanted to run 'always on'. After a while I put a few websites on it, and some websites for friends, and my little brother, and the local residents association, and next thing I knew I was running a load of instances of Wordpress. I was also constantly fending off the advances of hackers who were forever finding exploits in the famously insecure blogging platform. Recently I got around to removing the last of the sites from my web server and finally cancelled the contract and let it die the death it long deserved. The sites I have left under my control are now all static sites either, like this, generated by Docusaurus or purely static things that are built by Claude with a little help from me. All of it is now in S3 buckets hosted as static public websites. Being the github fanboi that I am I have put each of the static sites in a repository and it occurred to me that when I commit a change it would be great if I could just bash the whole lot over to S3 with an action.
The solution
The solution turned out to be pretty straightforward. GitHub Actions are perfect for this kind of automation - they can trigger on any repository event (like pushing code) and run whatever commands you need. In this case, we need something that will sync the contents of the repository to an S3 bucket whenever changes are pushed to the main branch. Here's how to set it up:
Step 1 - IAM
First, you'll need an AWS IAM user with permissions to write to your S3 bucket. Create one with this policy (replace 'your-bucket-name' with your actual bucket name):
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:PutObject",
"s3:GetObject",
"s3:ListBucket",
"s3:DeleteObject"
],
"Resource": [
"arn:aws:s3:::your-bucket-name",
"arn:aws:s3:::your-bucket-name/*"
]
}
]
}
Step 2 - Adding secrets to github
When AWS creates the IAM user, you'll get an Access Key ID and Secret Access Key. These need to be stored securely in your GitHub repository as secrets. Go to your repository settings, find the "Secrets and variables" section under "Actions", and add:
AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_REGION (e.g., 'us-east-1') S3_BUCKET (your bucket name)
Step 3 - The Action
Now for the fun part. Create a new file in your repository at .github/workflows/s3-sync.yml:
name: Sync to S3
on:
pull_request:
types:
- closed
branches:
- main # or your default branch name
jobs:
sync-to-s3:
# Only run if PR was merged (not just closed)
if: github.event.pull_request.merged == true
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ secrets.AWS_REGION }}
- name: Sync to S3
run: |
aws s3 sync . s3://${{ secrets.S3_BUCKET }} \
--delete \
--exclude ".git/*" \
--exclude ".github/*" \
--exclude "README.md" \
--exclude ".gitignore"
The summary
This workflow does exactly what we need:
It watches for any pushes to the main branch When triggered, it checks out your code Sets up AWS credentials (using your stored secrets) Syncs everything to your S3 bucket, excluding files we don't want on the website
The --delete flag is important - it ensures that if you delete files from your repository, they'll also be removed from S3. Without this, you'd end up with orphaned files in your bucket. Now, whenever you push changes to main (or merge a pull request), GitHub Actions automatically syncs everything to S3. No more manual uploads, no more FTP, no more wondering if you remembered to upload that last change.
It's all automated.
The best part? This setup works perfectly with static site generators like Docusaurus. You can have your build process run first, and then sync the built files to S3. I am pretty sure I can get Actions to do the build so I just commit my markdown change and go have tea and iced buns.