Skip to content

Architecture

This page explains how the running system is built and why. For the exact endpoints see the API Reference; for the cryptography see The matching protocol.

Vouch is a small monorepo with a deliberately thin, crypto-blind server. The device does the cryptography; the server only ever pairs users and compares opaque points.

  • Server — FastAPI on Python, backed by CloudNativePG Postgres 18. Alembic migrations run once per release as a Helm hook Job. A background matcher loop pairs preference-compatible users. Admin/debug endpoints sit behind a bootstrap token. Surrogate primary keys are UUIDv7, generated server-side by Postgres 18’s built-in uuidv7() — PG18 is a hard requirement.
  • Client — a React SPA served by nginx. All crypto runs in-browser via libsodium; cross-language test vectors keep the browser bit-compatible with the server.
  • Infra — k3s on two Graviton EC2 nodes (a public edge node and a private apps node), fck-nat for egress, Traefik with Let’s Encrypt for TLS, images in ECR. Everything is provisioned by OpenTofu, and helmfile converges the cluster.

The whole point of Vouch is that plaintext contacts and secret keys never leave the device. So the server is designed to hold as little as possible: it stores opaque 32-byte points, pairs users by preference, and hands each side the other’s published set. It learns only the cardinality of an intersection and index linkage — never a phone number. This is what the protocol buys, and it shapes every API: there is no endpoint that could return a contact, because the server never has one.

A request from the SPA hits Traefik on the edge node (TLS terminated with a Let’s Encrypt cert), is routed by host (app. → client, api. → FastAPI), and lands on the apps node. The API talks to the in-cluster Postgres. The background matcher runs inside the API process on a timer.

Internet
│ (TLS, Let's Encrypt)
┌────▼─────┐
│ Traefik │ edge node (public)
└────┬─────┘
app. │ api. │ hyperdx.
┌────────┴─────────┐
│ FastAPI · nginx SPA │ apps node (private)
│ CloudNativePG Postgres │
└───────────────────────────┘
│ egress
┌────▼────┐
│ fck-nat │
└─────────┘

The architecture is deliberately minimal and has documented gaps on the way to production — single nodes per role, no Postgres backups yet, and an unfinished observability hop. These are catalogued and prioritised in The road to production and Observability.