gazetteer.krisgrzepka.com / infrastructure

The backend was
replaced underneath.

A PHP and Apache container that had stopped working — three of its upstream APIs were dead or paywalled, and a free-tier host added twelve seconds to the first request. It now runs serverless, and the frontend never learned that anything changed.

Region eu-west-2 London Origin private S3 + OAC Cold start ~154ms Site checking… API checking…
Client

A visitor

Opens the map and picks a country

step 1
Edge · 400+ locations worldwide

Route 53

A subdomain on an existing hosted zone

Alias to CloudFront — no extra lookup to pay for

step 2

ACM certificate

Issued and DNS-validated by Terraform during the first apply

us-east-1, because CloudFront reads certificates there

step 3

CloudFront

Two origins behind one domain: the site, and the API

Default behaviour caches; the API prefix does not

Origin · eu-west-2 London

S3

The map, its tiles of country geometry, and the vendor scripts

Public access blocked — only CloudFront can read it, via OAC

on cache miss
Measured from your browser, just now
ms round trip

Fetching a file from the live site to time the real path…

Decisions worth defending

Route by path, not by rewrite
Giving CloudFront a second origin for /libs/php/* meant the entire backend could be replaced without touching a line of frontend code. The migration's blast radius was one Terraform file.
No dependencies is a feature
The handler uses only what Node 20 ships. There is no build step to break, no lockfile to audit and no package that can be compromised upstream — and it initialises in about 154ms.
Dead upstreams replaced, not patched around
Three providers had shut down or moved behind a paywall. Each was swapped for a keyless equivalent behind the same response shape, so the frontend kept parsing what it always had.
Keys left the repository
Credentials had been hardcoded in committed PHP. They are now Terraform variables injected as Lambda environment variables, and the tfvars file holding them is not tracked.