Skip to content

Run Vouch locally, end to end

This tutorial walks you from a fresh clone to two users matching through the Private Set Intersection protocol, all on your laptop, and then shows you the operator’s view of what just happened. You don’t need to understand the crypto to finish it. If you get curious about why it works, read The matching protocol afterwards.

Install these once:

  • uv — Python toolchain and runner
  • just — the task runner (just lists every recipe)
  • docker (with Compose) — for Postgres
  • Node 22 — for the web client
  • system libsodium — the crypto library the client vectors link against

From the repository root:

Terminal window
just dev

This starts Postgres 18 in Docker, applies the migrations, and runs the API on :8000 with development secrets (the admin bootstrap token is local-admin, SMS codes go to the log and to the admin outbox instead of a phone). Leave it running; in another terminal:

Terminal window
just web

Vite serves the web client on http://localhost:5173 and proxies the API.

Register in the browser with any username, a password, the two declarations (18 or older, privacy notice read) and local-admin in the admin bootstrap token field. Or from the terminal:

Terminal window
cd server
uv run vouch register root --admin-token local-admin --adult --accept-notice
uv run vouch whoami

The CLI keeps a real session (a rotating refresh cookie) under ~/.config/vouch/cli.json, exactly like the browser does.

The app sends you to phone verification first. Enter any E.164 number (+32470123456). The code went to the dev outbox; read it either in the browser at #/admin/sms or with:

Terminal window
uv run vouch admin sms-outbox

Type the code in. Behind the scenes the client proved, with a DLEQ proof, that the token it will publish is bound to the number you just verified.

Add a couple of contacts on the contacts tab and publish. Then let the simulator create a second, compatible user who shares one of them:

Terminal window
just demo # = vouch sim demo against http://localhost:8000

It registers two users, verifies their phones through the outbox, publishes their padded, encrypted contact sets, syncs until they match, prints PASS, and deletes them again. For a richer graph you can log in with, run just seed (six users, password hunter2hunter2).

Open http://localhost:5173/#/admin/overview: user, pair, match and report counts, what the background matcher last did, the migration revision, and the settings (secrets redacted). Browse users, open one, ban and unban them, read the SMS outbox, resolve reports, run the matcher by hand. The same view from the terminal:

Terminal window
uv run vouch admin overview
uv run vouch admin users --q demo
uv run vouch admin pairs
uv run vouch api GET /me/export # any authenticated call, JSON in/out

The interactive API docs are at http://localhost:8000/docs.

Terminal window
just check # everything CI runs: ruff, pytest, oxlint, tsc, vitest, helm, tofu
just server-unit # only the tests that need no database
just e2e # browser tests (Playwright): register → verify → publish → match, admin flows

The browser tests in client/e2e/ drive the real SPA against the real API in headless Chromium: two people register in separate browser contexts, verify their phones through the outbox, publish, and match; an admin bans and unbans a user; a sent code survives a reload. Every test creates its own users, so they run against any database, including a throwaway one in CI.

The server suite runs against a throwaway Postgres from testcontainers (or VOUCH_TEST_DATABASE_URL if you point it at one); it includes a migration-drift guard that applies the Alembic chain and diffs it against the models.

You ran the whole system: database, API, web client, a live match, and the admin surface. From here: