Blog Infrastructure

Overview Link to heading

This site is a statically generated Hugo blog hosted on AWS, deployed automatically via GitHub Actions on every push to main.

Write Markdown → push to main → GitHub Actions builds Hugo → syncs to S3 → CloudFront serves it

Architecture Link to heading

ComponentPurpose
HugoStatic site generator — turns Markdown into HTML
S3Stores the built HTML/CSS/JS files
CloudFrontCDN — serves the site over HTTPS globally
ACMTLS certificate for blog.joshua-does-things.com
CloudflareDNS — CNAME pointing to CloudFront (DNS-only, not proxied)
GitHub ActionsCI/CD — builds and deploys on push to main
SpaceliftManages Terraform state and infrastructure changes

Why CloudFront in front of S3? Link to heading

S3 static website hosting only supports HTTP. CloudFront provides HTTPS, a global CDN cache, and lets us use a custom domain with a proper TLS cert.

Why is the Cloudflare record not proxied? Link to heading

CloudFront handles TLS termination. Enabling Cloudflare’s proxy (orange cloud) on top of CloudFront creates a double-proxy that breaks certificate validation.


Repository Structure Link to heading

blog/
├── infra/                        # Terraform — infrastructure definition
│   ├── providers.tf              # AWS + Cloudflare provider config
│   ├── variables.tf              # Input variables (domain, zone ID, etc.)
│   ├── main.tf                   # S3, CloudFront, ACM, DNS, IAM
│   └── outputs.tf                # Bucket name, CF distribution ID, role ARN
└── site/                         # Hugo site root
    ├── hugo.toml                 # Site config, theme, nav, social links
    └── content/
        ├── posts/                # Blog posts
        ├── projects/             # Project write-ups
        ├── docs/                 # Personal documentation (you are here)
        └── about.md              # About page

Infrastructure Link to heading

Terraform lives in infra/ and is applied by Spacelift. To change infrastructure, edit the .tf files and push — Spacelift will plan and apply.

Spacelift environment variables required Link to heading

VariableDescription
TF_VAR_cloudflare_zone_idCloudflare Zone ID for joshua-does-things.com
TF_VAR_cloudflare_api_tokenCloudflare API token with Zone:DNS:Edit

GitHub Actions secrets required Link to heading

SecretWhere to get it
AWS_DEPLOY_ROLE_ARNterraform output github_actions_role_arn
S3_BUCKET_NAMEterraform output s3_bucket_name
CLOUDFRONT_DISTRIBUTION_IDterraform output cloudfront_distribution_id

CloudFront URL rewriting Link to heading

A CloudFront Function rewrites clean URLs to their index.html equivalents at the edge before the request reaches S3:

/posts/          → /posts/index.html
/docs/           → /docs/index.html
/about           → /about/index.html

Deployment Link to heading

Deployment is fully automatic. Push any change under site/ to main and GitHub Actions will:

  1. Check out the repo (including the hugo-coder theme submodule)
  2. Build the site with hugo --minify
  3. Sync the output to S3 with --delete (removes stale files)
  4. Invalidate the CloudFront cache so changes are live immediately

The workflow only triggers on changes to site/** — pushing infra-only changes won’t kick off a Hugo build.


Theme Link to heading

The site uses hugo-coder as a git submodule at site/themes/hugo-coder.

To update the theme:

git submodule update --remote site/themes/hugo-coder
git add site/themes/hugo-coder
git commit -m "Update hugo-coder theme"
git push

Local Development Link to heading

# First time only — pull the theme submodule
git submodule update --init --recursive

# Serve locally with live reload
cd site
hugo server

# Open http://localhost:1313

Draft posts (those with draft: true in frontmatter) are hidden by default. To preview them locally:

hugo server -D