Roots App

A private social platform where families share photos, videos, and prompted memories with each other, with no ads and no algorithmic feed. Built as a permanent family archive rather than a disappearing feed, and designed to stay usable across generations.

Co-Founder & Lead Engineer, February 2025 to present. Sole engineer on the product, and the author of the CI guards and review workflows that let a non-technical co-founder contribute UI code safely. Currently in TestFlight beta with hundreds on the waitlist.

TestFlight

Beta status

Self-hosted

CI runners

3,100+

Automated tests

3

Terraform environments

Architecture

Flutter mobile client communicating over HTTPS with roughly 120 API endpoints on Firebase Cloud Functions v2 on GCP. Firestore for structured data, Cloud Storage for media. Clean architecture with BLoC state management and dependency injection via GetIt.

Flutter Client (iOS/Android)
  |
  +-- BLoC state management
  |     +-- Repository interfaces
  |           +-- Services
  |                 +-- ApiClient (HTTP + auth interceptor)
  |
  |------- HTTPS -------|
  |
Firebase Cloud Functions v2 (API endpoints, background jobs)
  +-- Auth middleware (Firebase Admin SDK)
  +-- Family access middleware
  +-- Business logic handlers
  |
  +-- Firestore (compound indexes)
  +-- Cloud Storage (signed-URL delivery)
  +-- Firebase Auth (email/password, Google, Apple)
  +-- Cloud Scheduler + Cloud Tasks

Encryption: Designed, Scrapped, Formally Verified

A zero-knowledge encryption layer was designed for Roots and partially built into the backend before being scrapped. Roots does not offer end-to-end encryption and none is planned. The call was a market one rather than a technical one, made after talking with advisors about how much the target users would value it at this stage of the product. The protocol composes standard primitives (X25519, AES-GCM, Argon2id); no novel cryptography was invented.

I later reconstructed that design and put it through a full formal security analysis: a precise specification, machine-checked proofs in Tamarin, a game-based argument for the encryption core, and a tested Rust implementation. The analysis found a key-substitution attack available to a malicious server, the adversary the design named as its primary threat, and verified a strengthened handoff that closes it.

Read the full Roots ZKA analysis

Infrastructure

Terraform IaC

Three Firebase environments (dev/staging/prod) managed by Terraform with modular composition. Five modules (Storage, Firestore, IAM, KMS, Project APIs) are composed per-environment with shared variables. State stored in GCS with per-environment isolation.

roots-infra/
  environments/
  |  dev/       main.tf, providers.tf, variables.tf
  |  staging/   main.tf, providers.tf, variables.tf
  |  prod/      main.tf, providers.tf, variables.tf
  modules/
     storage/     5 buckets, lifecycle policies, CORS
     firestore/   database, security rules, indexes
     iam/         CI/CD service account, role bindings
     kms/         encryption key rings (prod)
     project-apis/  GCP API enablement

CI/CD

Seven GitHub Actions workflows across two repos. The Flutter CI pipeline runs analysis, formatting checks, and four parallel test categories (unit, widget, integration, API). Backend deploys are a reusable workflow that checks out the infra repo and runs `firebase deploy` against the target environment using per-environment service account keys. iOS builds use Fastlane with match for code signing and TestFlight distribution.

Push to main  ──>  CI (analyze + test matrix)  ──>  Deploy dev
Tag rc-v*     ──>  Build iOS (staging)  ──>  TestFlight   (current releases: TestFlight beta)
Tag v*        ──>  Build iOS (prod)     ──>  App Store    (release path, not yet shipped)

Backend deploy (reusable):
  checkout roots-infra  ──>  npm ci  ──>  firebase deploy
  (functions, firestore rules/indexes, storage rules)

Security

  • CI/CD service account per environment with least-privilege IAM
  • KMS encryption key rings configurable per environment, enabled in prod
  • Uniform bucket-level access with public access prevention enforced on all buckets
  • Flutter flavors with separate bundle IDs, Firebase configs, and signing profiles per environment

Self-hosted CI

I designed, built, and operate a personal, single-host CI setup that runs most of the pipeline on my own hardware. It is not a team platform: there is no high availability and no autoscaling. A scheduled router does move Linux jobs to GitHub-hosted runners when the box is offline, so the trade-off is cost and control for one engineer rather than uptime.

bazzite-ci runs self-hosted GitHub Actions as infrastructure-as-code on a Bazzite (Fedora atomic) Linux desktop. Three rootless-podman runners are defined as systemd Quadlet units and registered at the org level, so one pool serves both Roots and roots-infra. Each runner gets cgroup resource limits (memory, CPU, IO). A custom runner container image extends a base runner image and adds libsqlite3 and a git safe.directory fix, both of which resolved real CI failures. A systemd-timer scheduler auto-yields the desktop between CI, gaming, and idle/away states using GPU, process, and logind detection, and a localhost Python dashboard polls the GitHub API for runner and workflow health.

A separate self-hosted macOS runner produces signed Flutter iOS builds for TestFlight via Fastlane match with an App Store Connect API key. It runs under a dedicated non-admin account with keychain and SSH hardening.

Work is routed deliberately: iOS builds on the Mac, Linux tests on the Bazzite pool, and deploys, Terraform, and security scans on GitHub-hosted runners. The motivation is cost (self-hosting avoids roughly 10x hosted-macOS minutes) and control over the build environment.

MCP Servers

Two custom Model Context Protocol servers give development tooling structured access to the project's own API contracts and product requirements. Both are containerized with Docker and follow a dual-mode pattern (HTTP standalone or stdio for MCP-hub integration).

API Documentation Server

Four tools: list/get API schemas, list/get endpoint definitions. Loads Swagger documentation from the backend and exposes it as structured tools, so tooling queries the real API specs during development.

Epics Management Server

Four tools plus two resources: list/search epics and user stories, browse the epic catalog by module, view requirement-to-story mappings. Parses markdown specification files and caches results in memory.

Alongside these, the workflow uses a few off-the-shelf third-party MCP servers for documentation lookup, browser automation, and semantic code navigation.

Tech Stack

Frontend

Flutter, Dart, BLoC

Backend

Node.js, TypeScript, Cloud Functions v2

Database

Firestore, compound indexes, signed-URL media delivery

Storage

Cloud Storage, lifecycle tiering, upload pipeline

Infrastructure

Terraform, GitHub Actions, Docker

Security

Firebase Auth, IAM least-privilege, zero-knowledge E2EE (designed, formally analyzed)

Auth

Firebase Auth (email, Google, Apple OAuth)

AI Tooling

Claude Code, custom MCP servers (2)