The problem
A portfolio that claims “reliability-first infrastructure” should be able to prove it. The honest question a hiring manager asks is: how would you even know if this site went down? Without instrumentation, the answer is “a visitor would tell me” — which is the answer of someone who has never run anything in production.
So the goal here isn’t a dashboard for its own sake. It’s the full monitoring loop, end to end: measure the platform from the outside, alert a human when it breaks, and publish the evidence publicly. The constraint that makes it interesting is cost — real synthetic monitoring (CloudWatch Synthetics, Pingdom, Datadog) starts at tens of dollars a month, which is absurd for a site whose entire run-rate is under $2. Everything below stays inside the AWS free tier.
The architecture
A Lambda canary runs on a 10-minute EventBridge schedule. Each run issues an HTTPS GET to three targets — the site, the /api/visits endpoint, and the demos domain — and records three things per target: availability (status code), latency (wall-clock of the request), and, for the HTML targets, whether the expected security headers (HSTS, CSP, X-Frame-Options, Referrer-Policy, Permissions-Policy) are still present.
That data goes two places. It’s published as custom CloudWatch metrics (availability and latency, dimensioned by target), where Terraform-managed alarms watch it and page an SNS topic on two consecutive failed probes. And it’s appended to a rolling ~90-day results document in a dedicated S3 data bucket, served same-origin through a new CloudFront /data/* behavior — which is what the public status page fetches.
Key decisions & trade-offs
A Lambda canary instead of CloudWatch Synthetics. Synthetics canaries are the “correct” AWS answer and cost roughly $0.0012 per run — about $5/month at a 10-minute cadence, more than the entire rest of the platform. A hand-rolled Lambda doing the same three GETs costs fractions of a cent. The trade-off is that I own the probe logic (retries, header parsing, the results format) instead of getting a managed screenshot-and-HAR harness. For three endpoints, owning it is the right call — and it’s a better interview story than “I clicked the Synthetics wizard.”
Same-origin data, not a relaxed CSP. The status page is a static page under the site’s strict default-src 'self' policy. Rather than punch a hole in the CSP to reach an external metrics API, the canary writes its results into an S3 bucket that CloudFront serves at /data/* on the same distribution. The page fetches /data/status.json as a first-party request. No new origin, no CSP change, no third-party JavaScript — the security posture the site advertises stays intact.
A dedicated data bucket, because of the deploy hazard. The site deploys with aws s3 sync --delete, which prunes anything in the target bucket that isn’t in the build output. Lambda-written JSON in the site bucket would be deleted on the next deploy. So the canary writes to a separate bucket entirely, wired to CloudFront under its own OAC with a bucket policy scoped to the data/ prefix. The same bucket will back the upcoming FinOps and security-scorecard pages under their own prefixes.
Custom metrics kept under the free-tier line. Two metrics per target across three targets is six custom metrics — inside the 10 that CloudWatch gives for free. Availability alarms fire on the Minimum statistic over two consecutive 10-minute windows, so a single blip doesn’t page me at 3am, but a real outage does within ~20 minutes.
Cost breakdown
| Component | Monthly cost |
|---|---|
| Lambda canary (~4,400 invocations) | ~$0 (free tier) |
| Custom CloudWatch metrics (6) | $0 (≤ 10 free) |
| CloudWatch alarms (5) | $0 (≤ 10 free) |
| SNS email notifications | ~$0 |
S3 data bucket + CloudFront /data/* | fractional cents |
| Total | ≈ $0/mo |
What I’d do differently at scale
For more than a handful of endpoints, the hand-rolled canary stops being worth it — CloudWatch Synthetics’ managed screenshots, HAR capture, and multi-step scripted flows earn their fee once you’re monitoring real user journeys rather than liveness. I’d also move from a single-region probe to a few regions to distinguish “the site is down” from “us-east-1 can’t reach it,” and push the alarm fan-out through an incident tool (PagerDuty / Opsgenie) instead of raw SNS email once there’s an on-call rotation to respect. The rolling JSON-in-S3 results store is deliberately the simplest thing that works; at higher resolution it would become a time-series store (Timestream or Prometheus) with the status page querying an aggregation endpoint rather than replaying every sample in the browser.
Links
- Live status: andruxa.dev/status
- Repo: github.com/andruxap/resume-website