Using 11ty and GitHub Actions to build and deploy the site to AWS #
The goal of this project was to be able to simplify the creation of content. I didn't want to have to manage a separate Content Management System (CMS), so when I found 11ty I was over the moon. I can write the content in markdown files and 11ty combines them with nunjucks templates to make static HTML files.
Another goal, was to be able to maintain the site on a mobile device. The last thing I wanted was to be changing code and writing content on my desktop Mac. I need to be able to make any update on the go. With GitHub and GitHub Actions I am able to do all of this (except create new files) in the GitHub mobile app.
Finally, I wanted it all handed to me on a plate, so I cloned the @11ty/eleventy-base-blog and started from there. In retrospect I should have cloned @madrilene/eleventy-excellent because it looks great, uses some very clever design principles, and I could have modified the look and feel relatively easily (once the CSS had been de-minified of course).
I've made a few changes to the nunjucks templates and the CSS to personalise the site, but it's not diverged much from the base blog. I would love to make the repository public, but for reasons that will become clear, I need to keep it private for now. I'll look for workarounds to this in the future.
GitHub Actions are a game changer. An Action runs on a runner. The runner is an Ubuntu virtual machine (Windows and macOS options are also available, but why would you?) and on that VM you can clone your repository, install other utilities like Node and 11ty, and run command line utilities like cp
and pwd
(which is handy for troubleshooting).
Given that I'm using GitHub Actions to deploy to AWS, you may be forgiven for thinking that I'm storing my AWS secrets in GitHub Secrets. Not so! I am using Open ID Connect (OIDC) to establish trust between GitHub amd AWS, which allows GitHub to run CLI commands against my account as long as certain criteria are met, i.e. it's me running the command from the main branch of my repository.
Configuring OIDC #
- In AWS, as your IAM user, go to IAM and add a new identity provider. Here are the options you need to change from the defaults:
- provider type - OIDC
- provider url - https://token.actions.githubusercontent.com (also verify the thumbprint)
- audience - sts.amazonaws.com
- Still in IAM, create a new role with the following options:
- trusted entity type - Web Identity
- identity provider - token.actions.githubusercontent.com
- audience - sts.amazonaws.com
- permission policy - Administrator Access (over time you can reduced these permissions as you learn which permissions are essential)
- role name - GitHubActionsOIDCRole
- trust policy - add conditions to limit access to this role (add more conditions if you like)
"Condition": {
"ForAllValues:StringEquals": {
"token.actions.githubusercontent.com:actor": "repository-owner",
"token.actions.githubusercontent.com:sub": "repo:repository-owner/my.repository.co.uk:ref:refs/heads/main",
"token.actions.githubusercontent.com:iss": "https://token.actions.githubusercontent.com",
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
}
}
- Grab the arn of the GitHubActionsOIDCRole role for use later
Your AWS environment is now set up to accept commands from GitHub Actions
Configuring GitHub Actions #
In the cloned GitHub repository, create a new workflow in .github/workflows/
e.g. 11ty_deploy.yml
# A name for your workflow
name: 11ty_Deploy
# This makes the workflow run whenever a change is commited to the repository (unless the file is .generatedraft - see the next page for more information)
on:
push:
paths-ignore:
- '.generatedraft'
# These permissions are needed to i) interact with GitHub's OIDC Token endpoint, ii) read the contents of the repository
permissions:
id-token: write # This is required for aws-actions/configure-aws-credentials
contents: read # This is required for actions/checkout
# These are the jobs that will run on the latest version of Ubuntu (the runner)
jobs:
deploy:
runs-on: ubuntu-latest
steps:
# Clone the repository to the runner
- name: Git clone the repository
uses: actions/checkout@v3
# Install v19 of Node
- name: Install Node
uses: actions/setup-node@v3
with:
node-version: "19"
# Generate an empty Node project with all the defaults
- name: Init npm
run: 'npm init -y'
# Install 11ty
- name: Install 11ty
run: 'npm install @11ty/eleventy --save-dev'
# Execute 11ty against your repository (which has been cloned from 11ty/eleventy-base-blog or some other 11ty repository)
- name: Run 11ty
run: 'npx @11ty/eleventy'
# Using the trust relationship set up earlier, get a single-use token to access your AWS account
# The arn is the arn of the GitHubActionsOIDCRole role created earlier
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1-node16
with:
role-to-assume: arn:aws:iam::111111111111:role/GitHubActionsOIDCRole
role-session-name: 11ty_deploy
aws-region: eu-west-2
# The AWS CLI is installed by default on a GitHub Action runner so there's no need to install it
# Here we use aws s3 sync to upload the output of 11ty to the S3 bucket
# First sync images with a Cache Control max age of one year (to improve PageSpeed Insights performance score)
- name: Update Cache Control Header for images
run: 'aws s3 sync _site/img/ s3://www.sempercogitare.co.uk/img/ --cache-control "max-age=31536000"'
# Then sync font files with a Cache Control max age of one year
- name: Update Cache Control Header for fonts
run: 'aws s3 sync _site/font/ s3://www.sempercogitare.co.uk/font/ --cache-control "max-age=31536000"'
# Then sync the rest of the output of 11ty with the S3 bucket (it won't sync the images and fonts because they're already in sync
- name: Sync with S3
run: 'aws s3 sync _site/ s3://www.sempercogitare.co.uk'
# Invalidate the Cloudfront cache so you can see your changes straight away (get the Cloudfront distribution ID from AWS)
- name: Invalidate Cloudfront cache
run: 'aws cloudfront create-invalidation --distribution-id EEEEEEEEEEEEE --paths "/*"'
Once this has been set up, committing a change to a file in your repository will run the workflow above: 11ty will regenerate the static files and they will be sync'd with your S3 bucket. Finally, your Cloudfront cache will be invalidated, refreshing the site for everyone who views it.