← ALL PROJECTS

PROJECT 006 · CASE STUDY

McFly

LIVEBackend architect2026-07-01 · PYTHON · FASTAPI · POSTGRES · REACT · TYPESCRIPT · TERRAFORM · GCP
VISIT LIVE ↗

Our agency's own product, a multi-tenant project management and time tracking SaaS. I architected the entire backend and the infrastructure it runs on.

Problem

Agencies live and die by knowing where the hours go. McFly is our own product: multi-tenant project management and time tracking for teams like ours, with projects, tasks, time entries, clients, custom fields, templates, an automation engine, and Stripe-billed subscription tiers.

Building a SaaS as a bootstrapped agency sets an unusual constraint. The architecture has to be good enough to scale to paying customers, but the monthly bill has to stay near zero while it gets there. Most of the interesting decisions below fall out of holding both of those at once.

What I built

I architected the backend end to end: the service design, the data model, the background job system, and every piece of cloud infrastructure.

  • An async FastAPI service on Cloud Run, with SQLAlchemy 2.0 against Postgres and Alembic migrations. Around 24 domain modules and 60k lines of Python, each module following the same convention: model, schemas, endpoints, factory, tests, and a written feature definition beside the code.
  • Background jobs on TaskIQ against Valkey, with a deliberate split between pure business logic and the thin task wrappers that add queuing and retries. If the queue is down, endpoints still succeed and the work degrades gracefully instead of taking the API with it.
  • An event-driven automation engine: a registry of typed events and actions so business rules (“when a task moves to done, notify the client”) are declared, validated, and run asynchronously.
  • Role-based access from guest through admin, Firebase auth with custom claims, and TOTP two-factor checked entirely from those claims so the hot path never touches the database for it.
  • The whole platform in Terraform: eight composable modules covering networking, Cloud SQL, Cloud Run, secrets, Firebase, Cloudflare DNS, scheduling, and Stripe. Two fully isolated environments, each with its own VPC and database.

Decisions worth explaining

Terraform manages Stripe, not just the cloud. Products, pricing tiers, and the webhook endpoint are all declared in code, so billing config goes through review like everything else. That surfaced a genuine chicken-and-egg problem: the webhook secret must exist before the Cloud Run service that consumes it. The fix was computing the deterministic Cloud Run URL ahead of deployment so Terraform can create the webhook first. Nobody clicks anything in the Stripe dashboard, ever.

Self-managed Valkey and workers instead of managed services. Memorystore and managed queues would have tripled the infrastructure bill for a pre-revenue product. Instead, Valkey and the TaskIQ workers run on small Container-Optimised OS VMs inside the VPC, with no external ingress and SSH only through IAP. A Cloud Scheduler job even powers the dev environment down outside business hours, with Terraform told to ignore those state changes so the two systems don’t fight.

Postgres on private IP, auth at the application layer. Cloud SQL is reachable only through VPC peering, regional HA in production, zonal in dev. The API is publicly invocable but every request is verified against Firebase tokens, which keeps the IAM surface simple without leaving anything open.

Migration discipline as a hard gate. There is exactly one sanctioned script for creating migrations, and CI replays every migration one at a time against a fresh database. Ordering and idempotency bugs get caught before merge, not during a production deploy.

Outcome

McFly is live at mcfly.app, with a small team building on the architecture daily: 2,400+ commits on the backend, 38 migrations, and a React frontend and separate admin app consuming the API.

Stack

FastAPI and async SQLAlchemy on Cloud Run, Postgres, TaskIQ with Valkey, Firebase auth, Stripe billing, React and TypeScript frontends, and every piece of GCP infrastructure declared in Terraform.