How I Host OneBadBit
Saturday, Nov 16, 2024

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):

AWS diagram of my current blog architecture

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'