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 analysisInfrastructure
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 enablementCI/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)