Server Install
An evaluator-facing overview of standing up the mipo server stack and taking it from an empty host to a backed-up, restorable deployment. This is not the operational procedure.
Operators: use the guides in your release bundle
The step-by-step procedures are the files docs/install/from-source.md,
docs/operations/production-install.md and docs/operations/backup-restore.md
in the mipo source checkout or the release bundle your operator received (public publication of the
repository is pending). Those files are the authoritative reference. Follow them instead of this page.
Prerequisites
- A Linux host with Docker Engine 24 or newer and the Docker Compose plugin v2 (the docker compose subcommand). Kernel 5.4 or newer for TimescaleDB.
- Minimums: 2 CPU cores (4 for concurrent scans), 4 GB RAM, 40 GB disk plus room for scan history and backups, 2 GB swap.
- Inbound TCP 80 (redirect) and 443 (HTTPS; configurable with HTTPS_PORT) for users, plus TCP 8443 for the scanner mTLS ingress.
- Outbound: the image registry you pull from, or nothing at all when you build the images from source. Let's Encrypt endpoints only if you use ACME issuance.
- A hostname the server will answer on (for example mipo.example.com): the first install mints a self-signed certificate for it and scanners pin it.
Deploying the stack
mipo runs as one Docker Compose project: a Traefik edge, the manager, two ingest nodes, the dispatcher, the maintenance and backup services, a watchdog, the dedicated scanner-mTLS Traefik and one TimescaleDB cluster. The first install is a short sequence of explicit commands, run from the deployment directory; the architecture guide explains what each container does.
- Build or pull the images. From source: docker compose -f docker-compose.prod.yml build. Every service has a build block, so no private registry is needed.
- Mint the secrets file once: INGEST_URL=https://mipo.example.com CORS_ORIGINS=https://mipo.example.com bash scripts/install/bootstrap-env.sh --env prod --owner "$(id -un)". It writes a complete, 0600 .mipo.env and prints the SETUP_TOKEN once — copy it; the wizard needs it.
- Apply the schema: docker compose -f docker-compose.prod.yml --env-file .mipo.env --profile migrate run --rm migrator. App containers refuse to start against an un-migrated database.
- Bootstrap TLS: TLS_MIGRATE_SAN_HOST=mipo.example.com bash scripts/ops/tls-migrate.sh . — mandatory before the first up, or Traefik serves only its built-in ephemeral certificate.
- Start the stack: docker compose -f docker-compose.prod.yml --env-file .mipo.env up -d, then run the scanner-mTLS cutover (runCutoverCli.js run, the :8443 overlay, mark-published) as described in docs/install/from-source.md.
- Every later version change is one command on the deployment host: mipo.sh deploy --version <SHA> (DB_PASSWORD in the environment; COMPOSE_FILE defaults to /opt/mipo/docker-compose.prod.yml). It pulls, runs the preflight gate, quiesces the app, migrates and rolls out — see the upgrade guide.
The Day-0 setup wizard
- Open https://mipo.example.com. With no owner account yet, every request redirects to /setup.
- Enter the SETUP_TOKEN from the bootstrap. The wizard sends it with every setup request; the manager refuses to create an owner without it. If you lost it: grep '^SETUP_TOKEN=' .mipo.env on the host.
- Choose the owner username and password. The password field shows a live checklist of the policy (at least 12 characters); the same rule set is enforced by the API, so a password the checklist rejects cannot be saved.
- The owner bypasses RBAC scope checks and cannot be deleted. Create everyone else as a least-privilege user afterwards (or enable OIDC under Admin > Identity).
- To return to Day 0 — for example after a credential mistake — run mipo.sh reset --config and re-open the wizard. On the single-cluster layout both --config and --all wipe the one database volume.
The first backup
The backup container runs on a cron schedule once healthy, but do not wait for it: as the owner, open
Admin > Backups and trigger a backup, then confirm a bundle exists with
docker compose -f docker-compose.prod.yml exec backup ls /backups. Bundles are encrypted when a
backup encryption key is configured on that page; download one for an off-host copy before any risky
operation. Keep the ENCRYPTION_KEY from the host's .mipo.env with your backups —
the bootstrap mints a fresh one on every new host, and encrypted database cells (TLS configuration,
notification credentials, ACME keys) are unrecoverable without the original.
Restoring
A restore is destructive: it drops and rewrites the entire cluster from one bundle, with no partial or
per-schema scope. It is run with the operator CLI, from the deployment directory, with the database and
backup containers up: cd /opt/mipo && sudo ./restore.sh /path/to/mipo-backup-<timestamp>.tar.gz
(the same script lives at scripts/ops/restore.sh in a checkout). The script stops and removes
the application containers itself, loads the dump as the database owner, then runs the migrator preflight,
schema catch-up and the scanner-CA bundle reconciliation before you bring the app back. The setup wizard's
"Restore from backup" path exists only where the manager holds owner database credentials
(OWNER_DATABASE_URL), which the production compose deliberately does not set — on a
production host use restore.sh even for a fresh-host recovery.