Architecture
mipo is a small set of cooperating containers behind a single TLS edge, backed by one TimescaleDB cluster (three purpose-built schemas) on an isolated network. The public website (this site) runs on entirely separate infrastructure.
Request path
Users and scanner bootstrap traffic (provisioning and enrollment) arrive at :443, where the main
Traefik terminates TLS. It routes /scanner/* and /discovery/* to the ingest nodes
and everything else to the manager (the GUI and admin API, authenticated with session cookies). Scanner and
resolver runtime traffic — heartbeats, job claims, results, certificate renewal — arrives at
:8443, a dedicated Traefik process that requires a client certificate on every handshake. It
routes /scanner/* to ingest and /scanner/cert* to the manager, which owns the
certificate lifecycle. Scanners authenticate with that client certificate plus their API key; a firewall
between a scanner host and mipo must allow both ports (see
Scanner ACL).
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.
Services
The manager serves the GUI and admin API as a single instance. Two ingest
nodes handle heartbeats, job dispatch, and result collection from scanners and resolvers, and serve the
discovery-source push API (/discovery/lists/*) that external inventories post addresses to. The
dispatcher is a singleton that orchestrates scans — resolving templates into concrete
targets, expanding CIDR ranges, batching jobs across scanners, queueing reverse-DNS lookups, and reconciling
stalls. The backup service performs scheduled database backups, encrypted when a backup
encryption key is configured, and exposes an internal-only HTTP API; restore is a separate path, the operator
CLI scripts/ops/restore.sh, which connects as the database owner (the setup wizard's restore
exists only where the manager is given owner credentials, which the production compose does not do).
Four more services complete a production deployment. The maintenance service is the only
long-running process that connects as the database owner: it prunes evidence past its retention window and
has no HTTP port. The migrator is a one-shot container run before the app starts; it applies
forward-only schema migrations, reconciles the per-service role grants, and offers a preflight
safety check that upgrades use as a gate. The watchdog is a sidecar that restarts unhealthy
containers, collects metrics, and publishes the scanner trust bundle to the edge. A second Traefik process,
traefik-scanner-mtls, serves the :8443 scanner ingress described above, kept
separate so its mandatory client-certificate requirement can never be relaxed by the main edge's
configuration. The manager, ingest, dispatcher, maintenance and migrator all run from one application
image, selected per container by a ROLE variable; backup and watchdog are separate images.
Data layer
One TimescaleDB cluster holds three schemas that isolate concerns by access pattern: config (low-volume complex queries), results (TimescaleDB hypertables, time-series scan data retained for historical diffing), and jobs (scan and DNS orchestration state plus the append-only execution receipts). The cluster lives on an isolated internal network with no outbound internet — it cannot be reached from outside or reach out itself.
Each application service connects as its own database role with its own password — the manager, ingest, dispatcher and watchdog hold only their role's credentials, so a compromised process cannot log in as the cluster owner. The evidence tables — audit events, scan results, scan and DNS job receipts, and DNS observations — are append-only for every application role: no role holds update or delete privileges, and a database trigger rejects mutation even on a privileged path. Only the separate maintenance service, running as the owner, removes evidence, and only once it has passed its retention window.
Website separation
The marketing and docs site is Astro static output deployed to its own host. It is not behind Traefik, shares no runtime, database, or request path with the application, and serves only pure HTML, CSS, and JavaScript. This keeps the public surface entirely decoupled from the operational system.
Scanner lifecycle
Scanners are provisioned via a one-time install command, then heartbeat every 60 seconds (2 minutes of silence marks them offline). The dispatcher fans fully-resolved jobs out to the right scanners; for scanner groups it creates a separate job per scanner so the same targets are scanned from every vantage point. Scanners stream results back through ingest and never write the database directly. A background reconciler flags stalled jobs and emits recovery events. Resolver identities (reverse-DNS workers) share this whole lifecycle — provisioning, heartbeats, mTLS, revocation — but the dispatcher never hands them port work. When a scan template (or the global default in Admin > Settings) requests reverse DNS, the dispatcher derives the hosts from the scan, bundles DNS jobs per resolver group, and the resolvers in that group claim them through ingest, run PTR lookups against their host's DNS or a configured nameserver group, and submit observation batches plus a signed receipt per job. See Resolvers, Resolver Groups and Nameserver Groups.