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
| Component | Purpose |
|---|---|
| Hugo | Static site generator — turns Markdown into HTML |
| S3 | Stores the built HTML/CSS/JS files |
| CloudFront | CDN — serves the site over HTTPS globally |
| ACM | TLS certificate for blog.joshua-does-things.com |
| Cloudflare | DNS — CNAME pointing to CloudFront (DNS-only, not proxied) |
| GitHub Actions | CI/CD — builds and deploys on push to main |
| Spacelift | Manages 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
| Variable | Description |
|---|---|
TF_VAR_cloudflare_zone_id | Cloudflare Zone ID for joshua-does-things.com |
TF_VAR_cloudflare_api_token | Cloudflare API token with Zone:DNS:Edit |
GitHub Actions secrets required Link to heading
| Secret | Where to get it |
|---|---|
AWS_DEPLOY_ROLE_ARN | terraform output github_actions_role_arn |
S3_BUCKET_NAME | terraform output s3_bucket_name |
CLOUDFRONT_DISTRIBUTION_ID | terraform 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:
- Check out the repo (including the
hugo-codertheme submodule) - Build the site with
hugo --minify - Sync the output to S3 with
--delete(removes stale files) - 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