Mike Wilkerson writes here

How I Host OneBadBit


Update, August 2025: I've migrated to Bear Blog, so this post is no longer accurate.


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

       +---------------------+
       |   Site Visitors     |
       |  (Browsers/Clients) |
       +----------+----------+
                  |
                  v  DNS lookup
      +-----------+-----------+
      |     Amazon Route 53   |
      |   (onebadbit.com DNS) |
      +-----------+-----------+
                  |
                  v  HTTPS (ACM cert)
  +---------------+---------------+
  |       Amazon CloudFront       |
  |      (CDN Distribution)       |
  +---------------+---------------+
                  |
      Origin Access Control (OAC)
                  |
                  v
+-----------------+-----------------+
|     Amazon S3 (Static Website)    |
+-----------------+-----------------+

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.

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'

#software