Skip to main content

How to set up Github Actions to publish to S3 website

· 5 min read
Simon Painter
Cloud Network Architect

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've 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, I needed something that would sync the contents of the repository to an S3 bucket whenever changes are pushed to the main branch.

Here's how I 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)

I can't stress enough how important it is to use secrets for these values rather than hardcoding them into your workflows. Anyone with access to your repository could potentially access your AWS resources if you're not careful.

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 I needed:

  1. It watches for any merged pull requests to the main branch
  2. When triggered, it checks out my code
  3. Sets up AWS credentials (using my stored secrets)
  4. Syncs everything to my S3 bucket, excluding files I don't want on the website

The --delete flag is particularly important - it ensures that if I delete files from my repository, they'll also be removed from S3. Without this, I'd end up with orphaned files in my bucket cluttering things up.

Now, whenever I 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 I remembered to upload that last change.

It's all automated, and I love it.

Taking it further

The best part? This setup works perfectly with static site generators like Docusaurus. I've since expanded my workflow to also handle the build process. Here's a quick example of how I've modified the workflow to build my Docusaurus site before syncing to S3:

# Additional steps for Docusaurus
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'

- name: Install dependencies
run: npm ci

- name: Build site
run: npm run build

- name: Sync to S3
run: |
aws s3 sync ./build/ s3://${{ secrets.S3_BUCKET }} \
--delete

With this modification, I just commit my markdown changes, push them, and go have tea and iced buns while GitHub Actions builds my site and publishes it for me. It's a beautiful thing.

Have you set up similar automations for your projects? I'd love to hear how you've tackled this - drop me a comment or reach out if you have any questions about this setup!