How mipo is built
Users reach the manager through Traefik on :443; scanners and resolvers reach ingest through a dedicated mutual-TLS ingress on :8443; the dispatcher orchestrates scans; one TimescaleDB cluster with three purpose-built schemas lives on an isolated network. The public website runs on separate infrastructure.
Edge, services, data — and a detached public site
The public website (this site) is static HTML/CSS/JS deployed to its own host. It shares no runtime, no database, and no request path with the mipo application.
What each container does
| Service | Role |
|---|---|
traefik | The :443 edge: TLS termination, path routing and load balancing for users, and for scanner provisioning and enrollment (the bootstrap channel). |
traefik-scanner-mtls | A second Traefik process on :8443 for scanner and resolver runtime traffic. Every handshake must present a client certificate; /scanner/* routes to ingest, /scanner/cert* to the manager. |
manager | GUI + admin API (session cookies). Single instance. |
ingest ×2 | Scanner API (client certificate + API key): heartbeats, job claims, result and receipt collection. Two fixed nodes behind the mTLS ingress. |
dispatcher | Scan orchestration: resolves templates, batches jobs, reconciles stalls, schedules reverse-DNS work. Singleton. |
maintenance | Owner-run evidence retention: the one service that prunes the append-only tables. No HTTP, no outbound. Singleton. |
watchdog | Python sidecar for platform health: container auto-heal, metrics, restart recovery, and activation of the scanner-CA trust bundle the :8443 ingress verifies against. |
migrator | One-shot: applies schema migrations and reconciles the per-service role grants before any app container starts; also runs the pre-upgrade preflight. |
backup | Encrypted database backup (scheduled bundles); internal HTTP API. Restore is the operator CLI scripts/ops/restore.sh, which connects as the database owner. |
db | One TimescaleDB cluster (database mipo) with config / results / jobs schemas, on an isolated internal network. Hypertables live only in the results schema. |
The five Node services — manager, ingest, dispatcher, maintenance, migrator — run from one unified image; a
ROLE variable selects the entry point per container. Backup (shell + pg_dump) and the watchdog
(Python) are separate images. A Docker socket proxy and a one-shot scanner-CA reconcile job complete the stack.
Three Docker networks, one isolated database cluster
external (mipo_external)
Bridge with outbound NAT for services that need internet or external-facing ports: the :443 Traefik, manager, backup, watchdog, and the Docker socket proxy Traefik reads routes from.
internal (mipo_internal)
Isolated, no outbound internet. The database cluster plus everything that talks to it: manager, both ingest nodes, dispatcher, maintenance, backup, migrator, watchdog. The database cannot be reached from outside or reach out.
scanner-proxy
A fixed-subnet network for the mTLS path: both Traefik processes, the manager, and both ingest nodes. The pinned proxy address lets the backends verify that scanner traffic really arrived through the :8443 ingress.
Only the manager sits on all three networks. The ingest nodes are on scanner-proxy and internal — never on external — so scanner traffic can only arrive through the mTLS ingress. The dispatcher and maintenance are internal-only; backup and the watchdog bridge external and internal.
Per-service roles, append-only evidence
The migrator creates five Postgres login roles — mipo_manager, mipo_ingest,
mipo_dispatcher, mipo_ops, mipo_watchdog — each with its own
password, and a container holds only its own role's credentials. The cluster owner is reserved for
schema migrations, evidence retention, and break-glass access.
Six chain-of-custody tables are append-only: audit events, scan results, scan job receipts, DNS job receipts, DNS observation batches, and DNS observations. No application role is granted UPDATE or DELETE on them, and a BEFORE UPDATE/DELETE trigger blocks mutation even on a privileged path. Only the owner-run maintenance service prunes them, on the documented retention windows.
Provision → heartbeat → dispatch → report → receipt → reconcile
- Provision — the manager generates a one-time install command whose token is valid for one hour. The installer verifies the binary signature, generates the host's own key pair and API key, and sends a certificate request; the manager signs it and the host persists its client certificate.
- Heartbeat — every 60s the scanner reports liveness + system metrics to ingest over :8443; 2 minutes of silence marks it offline.
- Dispatch — the dispatcher fans fully-resolved jobs out to the right scanners (one job per scanner for groups).
- Report — scanners stream results back through ingest; scanners never write the database directly.
- Receipt — each job ends with a receipt signed by the scanner; the server recomputes every hash before accepting it.
- Reconcile — a background reconciler flags stalled jobs and emits recovery events.
Read the architecture guide
A long-form reference covers the topology, request path, and trust boundaries in depth.