Skip to content

System Architecture Overview

Claude edited this page Sep 14, 2026 · 2 revisions

System Architecture Overview

                         ┌─────────────┐
                         │   Browser   │
                         └──────┬──────┘
                                │ HTTPS (reverse-proxied)
                         ┌──────▼──────┐
                         │     api     │  FastAPI, serves the built SPA too
                         └──┬───────┬──┘
                            │       │
                 ┌──────────▼─┐   ┌─▼──────────┐
                 │  postgres  │   │  resolver  │  dedicated Unbound instance
                 │  (RLS)     │   │ (DNSSEC-   │  — every DNS check query goes
                 └──────────▲─┘   │ validating)│    through this, not the
                            │     └────────────┘    host's own resolver
                 ┌──────────┴─┐
                 │   worker   │  N replicas, off a Postgres work queue: mailbox
                 │            │  polling, DNS check sweep, domain verification
                 └────────────┘  sweep, retention purge — no message queue

api and worker are the same Docker image (backend/Dockerfile) run with different commands; a one-off migrate service (same image again) runs Alembic migrations and bootstraps the first platform-admin account before either starts. There's no separate frontend server — Vite builds the SPA at image-build time and FastAPI serves the static output directly (app/main.py).

Why no message broker (Redis/Celery/RabbitMQ)? Reusing the datastore you already run and have hardened (RLS, encryption-at-rest, backups) keeps the attack surface and the ops burden down. The background_jobs Postgres queue (SELECT ... FOR UPDATE SKIP LOCKED) comfortably handles this workload's cadence; it's an internal abstraction that could move to a broker later if a high-throughput need appeared. See Scaling Horizontally for how this scales in practice.

Why a dedicated resolver? Every DNS best-practice check (SPF, DKIM, DMARC, MTA-STS, DANE...) needs DNSSEC-validated answers to be trustworthy, and needs its cache tuned independently from whatever the host's own resolver is doing for everything else. Running a purpose-built Unbound instance keeps that isolated and tunable (resolver/overrides.conf).

Package-level dependency detail and licenses → Tech Stack & Licenses.


← Architecture & Internals · Home

Clone this wiki locally