This blog is a static site, built using Hugo and my own custom theme (I started with an existing minimal theme and modified it).
Architecture
The site is hosted in AWS as static files in S3 that are served by CloudFront (AWS’s managed CDN offering):
What I like about it
It’s more work than just pushing a static site up to e.g. Netlify, but I like that I have more control over my infrastructure and (after the initial setup cost) a super easy update flow.
- My total AWS bill is $1/month, plus whatever the annual recurring domain name renewal cost is.
- The simplicity of a static site:
- No server side component that can be compromised
- No networking components (VPC, subnets, route tables, security groups, etc.)
- No security updates or server maintenance needed
- S3 + CloudFront offer high scalability if I need it, for a low cost and low infrastructure overhead.
- Reliable: I deployed this back in 2018 or 2019, and it just works without a single issue.
- Low ongoing maintenance: Everything from domain to SSL cert (including auto-renew) to hosting is in AWS, so everything plays nicely together with no effort.
Workflow
New content
I use a Makefile for orchestrating all of the actions needed for managing my site, for example creating a new post Markdown file nested in the proper year/month directory structure:
new: ## Create a new post
@hugo new "posts/$(shell date '+%Y/%m')/changeme.md"
Local development
To test my work locally I use make run
to launch a local Hugo development server:
run: ## Run a local development server, showing all draft content
@hugo server -D -p 8080
Deploying
Once I’m happy with the changes, I commit and push to my GitHub repo, and then use a GitHub Actions job to push the content to S3:
name: Deploy to onebadbit.com
on:
workflow_dispatch:
concurrency:
group: "deploy"
cancel-in-progress: false
defaults:
run:
shell: bash
jobs:
deploy:
runs-on: ubuntu-latest
env:
HUGO_VERSION: 0.128.0
steps:
- name: Install the Hugo CLI
run: |
wget -O ${{ runner.temp }}/hugo.deb https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_extended_${HUGO_VERSION}_linux-amd64.deb \
&& sudo dpkg -i ${{ runner.temp }}/hugo.deb
- name: Checkout
uses: actions/checkout@v4
with:
submodules: recursive
- name: Build site with Hugo
env:
HUGO_CACHEDIR: ${{ runner.temp }}/hugo_cache
HUGO_ENVIRONMENT: production
run: |
hugo --minify
- name: Sync to S3
run: |
aws s3 sync ./public s3://onebadbit.com/ --delete
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
AWS_DEFAULT_REGION: 'us-east-1'
- name: Invalidate Cloudfront cache
run: |
aws cloudfront create-invalidation --distribution-id "${{ secrets.CLOUDFRONT_DIST_ID }}" --paths '/*'
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
AWS_DEFAULT_REGION: 'us-east-1'