This Site's Architecture
This portfolio is itself a live, low-cost GCP reference architecture: a FastAPI app on Cloud Run, provisioned with Terraform, deployed via GitHub Actions using Workload Identity Federation (no service account keys). The dashboard below is powered by a small real-time pipeline that tracks visits to this very page — Pub/Sub → Cloud Functions → BigQuery → Looker Studio.
Diagram
Cost breakdown
Real GCP free-tier limits, checked against this site's actual usage.
| Component | Free tier | This site's usage | Cost |
|---|---|---|---|
| Cloud Run (site) | 2M requests, 180K vCPU-s, 360K GiB-s/mo — shared across Cloud Run + both Cloud Functions | A few hundred visits/month, scale-to-zero | $0 |
| Collector Function | 2M requests, 180K vCPU-s, 360K GiB-s/mo — shared across Cloud Run + both Cloud Functions | One request per page view | $0 |
| Loader Function | 2M requests, 180K vCPU-s, 360K GiB-s/mo — shared across Cloud Run + both Cloud Functions | One Pub/Sub-triggered run per page view | $0 |
| Pub/Sub | 10 GB message throughput/mo | A few KB per event | $0 |
| BigQuery — storage | 10 GB/mo | A few MB of event rows | $0 |
| BigQuery — queries | 1 TB scanned/mo | Dashboard refreshes | $0 |
| BigQuery — streaming inserts | No free tier — $0.01/200MB | ~KB-scale writes | ~$0.00 (rounds to zero, but not technically free) |
| Artifact Registry | 0.5 GB storage | 3 kept image versions (~150–250MB each) | $0, headroom being the main thing to watch |
| Cloud Storage (function source) | 5 GB — only in us-* regions; this bucket is in europe-west8, so not covered | A few KB of zipped source, force_destroy | ~$0.00 |
| GitHub Actions | 2,000 free minutes/mo | One build per push | $0 |
| Looker Studio | Free product | Embedded dashboard | $0 (queries count toward BigQuery above) |
Total: effectively $0/month. The one honest caveat is BigQuery streaming inserts — billed, but at a volume too small to show up as a real number.
Design decisions
A few of the choices behind this architecture, and what they were weighed against.
Python end-to-end, not a JS framework
FastAPI + Jinja2 for the site, Python for both Cloud Functions. Python is my strongest language, so this keeps a solo project from context-switching, and doubles as a backend-skills demo rather than just a frontend one.
Region: europe-west8 (Milan), not europe-west1 (Belgium)
Both are free-tier equivalent at this traffic level — Milan wins on latency from home, plus a small "home turf" story for interviews.
Workload Identity Federation, not service account keys
GitHub Actions authenticates via short-lived OIDC tokens scoped to one repo (enforced by an attribute_condition on the provider) — no long-lived JSON keys sitting in GitHub secrets.
GitHub Actions + Artifact Registry, not Cloud Build
Avoids Cloud Build costs entirely while still giving a real, working CI/CD pipeline — free for public repos, and a broadly transferable skill.
Event-driven Pub/Sub pipeline, not a direct write to BigQuery
The collector function could write straight to BigQuery. It publishes to Pub/Sub instead, because the point of this project is to demonstrate the event-driven pattern itself, not just get analytics working.
Public collector endpoint: an accepted trade-off, not an oversight
A browser beacon can't authenticate, so the collector function is public. Cloud Armor rate limiting was rejected (not free-tier); routing through the FastAPI server was rejected (adds complexity for a low-value-abuse surface). Same exposure level as the site's own public Cloud Run invoker binding.
Monitoring & observability
Cloud Run and Cloud Run functions collect these metrics automatically — request count, latency percentiles, container instance count — with no setup and no extra cost. No custom dashboard needed.
Live visitor analytics
How this was built
This project was built in close collaboration with Claude, Anthropic's AI assistant — used deliberately, not just to generate code. Demonstrating that kind of collaboration was one of the explicit goals here, alongside the GCP architecture itself.
In practice that meant iterating on Terraform and app code with Claude while I was still learning Terraform, having it fact-check specific claims — GCP free-tier limits, icon licensing — against live sources rather than trusting recalled numbers, debugging real failures together (a container OOM crash, a CORS mismatch across Cloud Run's two default URLs, a git history conflict from an amended commit), and iterating on visual design (the architecture diagram, using official Google Cloud icons) over several rounds of feedback.
The skill on display isn't "AI wrote this" — it's directing an AI assistant on a real system: catching when a generated file path didn't match my actual repo structure, deciding which claims needed verification before publishing, and keeping ownership of every architectural decision.