How this site is built
Serverless on AWS for about nine dollars a month, where Go owns the markdown and JavaScript never touches it.
This site is a static front end on S3 behind CloudFront, with a Go service on Lambda for the handful of things that are genuinely dynamic. It costs roughly nine dollars a month, and seven of those dollars are AWS WAF.
I picked serverless deliberately, and not because it is fashionable. My background is Kubernetes, Linux, and on-premises infrastructure. Building this on a container host would have re-proven something I have already proven several times over. Serverless fills an actual gap, costs less than a VPS, and generates its own writing material.
Go owns markdown, not JavaScript
The decision that shapes everything else: a Go program walks the content directory, validates frontmatter, lints for leaked infrastructure detail, renders markdown to HTML with goldmark and Chroma, and emits a single JSON file. The web application never parses markdown.
That guarantees the search index and the rendered page come from one parser. The alternative — markdown parsed in Go for the API and again in JavaScript for the site — quietly produces search results whose text does not match the page they link to. It is the sort of bug that takes an afternoon to find and five minutes to fix, repeatedly, forever.
It also means frontmatter validation fails in exactly one place, with one error format, anchored to a file and line.
The build fails on things that matter
A few rules are enforced by the compiler rather than by discipline.
A case study with no measured outcome does not build:
outcomes:
- metric: "Mean time to restore"
before: "~40 min (manual)"
after: "< 90 s"
Without that field, a case study drifts into a list of technologies I touched, which proves nothing. The same data feeds the outcome strip pinned to the left rail, the cards on the index, and the generated preview image.
Leaked infrastructure detail also fails the build. The linter scans for RFC1918 addresses, internal FQDNs, tenant identifiers, AWS keys, GitHub tokens, PEM headers, and JWTs — inside code fences too, because a pasted terminal transcript is where this actually happens:
# This fails the build, by design.
ssh admin@10.125.254.6 # sanitize:allow -- deliberate illustration
That fence needed an explicit sanitize:allow comment to ship, because the
linter caught it while I was writing this paragraph. The escape hatch is
per-line and has to be typed on purpose, which is the property you want: an
audited exception rather than a global off switch.
Two details matter there. The first is that matches are redacted in the build output; a CI log that helpfully prints the secret it just caught has only moved the leak. The second is that employer hostnames and colleague names live in an uncommitted local denylist, so the denylist never leaks the names it protects.
Where the money goes
| Item | Monthly |
|---|---|
| WAF web ACL and two rules | $7.00 |
| Route 53 hosted zone | $0.50 |
| Domain, amortised | ~$1.25 |
| Lambda, S3, DynamoDB, CloudFront | ~$0.23 |
The interesting risk in a serverless design is not downtime. It is denial of wallet: an attacker cannot take the site down, but they can run up the bill.
The layer people reach for first is reserved concurrency, and it is worth being precise about what that does. It caps parallelism, not spend. Five concurrent invocations at a gigabyte, running flat out for a month, is about $173. Budgets are advisory and lag by up to a day.
What actually bounds the invoice is a kill switch: a CloudWatch alarm on concurrent executions triggers a Lambda that sets reserved concurrency to zero. The API goes dark and the static site keeps serving, which is the correct failure mode for a site that is mostly prose.
Things that bit
Origin Access Control forbids forwarding the Host header and rewrites
Authorization. Design a feature around either and you will find out late.
AWS permits exactly one dimensional cost-anomaly monitor per account, and creates one for you. Terraform’s attempt to create a second fails with a message that does not mention the one already there.
Immediate anomaly alerts only support SNS, never email. This matters less than it sounds: Cost Explorer’s underlying data lags by hours regardless, so “immediate” was never going to be immediate.
Chroma emits its token-name comment on the same line as the rule. Treating a
leading /* as a comment-only line therefore skips every rule in the file, and
produces a stylesheet that looks plausible and highlights nothing.
What is still missing
Search, view counts, a contact form, and per-post reactions all live in the Go service and are not deployed yet. Neither is the WAF — the number above is what it will cost, not what it costs today.
The site is built in the open. The interesting files are the content compiler, the Terraform modules, and the CloudFront function that supplies directory-index behaviour the private bucket cannot.