Migrating to IRIS v3
IRIS v3 is a major release. Compared with v2.4.x it changes:
- Deployment layout —
iris-webbecomes a slim meta-repository with two git submodules (iris-backendandiris-frontend); v2 was a single monolithic Flask application. - UI stack — the jQuery-based UI served by the Flask app is replaced with a dedicated SvelteKit SSR service running in its own container.
- Database engine — the bundled PostgreSQL image jumps from 12 to 18. This requires a one-time dump-and-restore migration; a PG18 server cannot read a PG12 data directory.
- Container image names — the published images at
ghcr.io/dfir-iris/*are renamed fromiriswebapp_*toiris-*and now cover four artefacts (iris-backend,iris-frontend,iris-db,iris-nginx). - Data model — new tables for war rooms, alert clusters, cluster rules, investigation flows, custom dashboards, notifications, mail rules, and case timelines. All schema changes apply via Alembic on the first boot of the new
appcontainer. - API surface — API version stays
v2.1, and v3 adds ~211 new/api/v2/*endpoints. The legacy/case/*,/alerts/*,/datastore/*,/dim/*,/global/*, and non-/api/v2/manage/*endpoints no longer work: nginx routes everything that is not/api/*to the new frontend service, so those paths never reach the backend./api/v2is the only supported API surface in v3.
Port your integrations to /api/v2 before you upgrade
Any script, webhook, or module still calling a legacy path will break the moment v3 comes up. The legacy endpoint migration map lists each legacy path next to its /api/v2 replacement.
Read this end-to-end before you start
The database engine jump is not reversible in place. Always take both a cold volume tarball and a pg_dumpall before starting, and rehearse on a staging copy first.
Deployment topology (v2.4.x → v3)
| Component | v2.4.x | v3.0.0-beta |
|---|---|---|
| Repository layout | Single repo dfir-iris/iris-web containing backend + jQuery UI + docker/nginx configs |
Meta repo dfir-iris/iris-web with iris-backend and iris-frontend as git submodules |
| Containers in the stack | 5 — app, worker, db, rabbitmq, nginx |
6 — app, worker, db, rabbitmq, nginx, frontend (SvelteKit SSR) |
| Published images | ghcr.io/dfir-iris/iriswebapp_{app,db,nginx} |
ghcr.io/dfir-iris/iris-{backend,db,nginx,frontend} |
Container names (default container_name:) |
iriswebapp_* |
iris_* |
PostgreSQL major version (bundled iris-db image) |
12 | 18 |
| Client authentication scheme (bundled DB) | md5 |
scram-sha-256 |
| Version pin | Ad-hoc per-service image tags | Single IRIS_VERSION variable in .env pins all four images together |
| Environment template | .env.model |
.env.example (meta repo root) |
The frontend service listens on port 5173 internally and is proxied by nginx for every URL that isn't /api/*. Operators don't interact with it directly — the docker-compose.yml wires it, its healthcheck gates nginx startup, and its env vars (PUBLIC_EXTERNAL_API_URL, ORIGIN, BODY_SIZE_LIMIT) get sensible defaults from IRIS_HOSTNAME.
Recommended upgrade path
The safe path is v2.4.x → v3 in a single hop, in staging first and then production. Do not mix a v3 database with a v2 app container — the schemas diverge.
The detailed step-by-step procedure — including the automated pg_dumpall/restore script, the volume-swap sequence, and three tiers of rollback — lives in the meta-repo:
📘 iris-web/docs/upgrade-to-3.0.0.md
That document is the operational source of truth. What follows here summarises the phases and highlights the doc-site-specific considerations (integrations, API changes, rollback semantics).
Phase overview
- Pre-flight — confirm you are on a recent v2.4.x (walk older instances up to v2.4.29 first), schedule a maintenance window, verify ~3× the size of your
db_datavolume is free on the host. - Snapshot — take both a cold tarball of the docker volume and a logical
pg_dumpall. The migration script does this automatically; if you are running it manually, do not skip either. - Update the source tree —
git checkout v3.0.0-beta.1oniris-web, thengit submodule update --init --recursive. - Migrate
.env— v3 introduces several new required variables (see the configuration reference and the meta-repo doc's §3.2b). A recycled v2.envwill boot with wrong defaults or fail. - Migrate TLS material — copy or regenerate the cert into
certificates/web_certificates/. See configuration §Certificates. - Run the migration script —
./scripts/upgrade-db-pg12-to-pg18.sh. It is idempotent and restartable; each of its five stages (pre-flight, cold tarball,pg_dumpall, volume swap, PG18 restore) detects prior completion and skips. - Bring up v3 —
docker compose up -d. Theappcontainer runs Alembic on first boot. - Verify — API smoke-tests (see below), then browser check of one existing case.
Post-migration smoke tests
# API answers
curl -fsSL https://iris.example.com/api/ping
curl -fsSL https://iris.example.com/api/versions
# Auth
curl -fsSL -H "Authorization: Bearer $IRIS_API_KEY" \
https://iris.example.com/api/v2/auth/whoami
# Cases list — should return 200 and JSON
curl -fsSL -H "Authorization: Bearer $IRIS_API_KEY" \
"https://iris.example.com/api/v2/cases?per_page=1"
# War-room readiness — new subsystem, should return 200 and JSON
curl -fsSL -H "Authorization: Bearer $IRIS_API_KEY" \
"https://iris.example.com/api/v2/war-rooms?per_page=1"
# PostgreSQL version — should report 18.x
docker compose exec db psql -U "$POSTGRES_USER" -d iris_db -c 'SELECT version();'
Rollback
Three tiers, in order of preference:
- Backup volume replay (fastest). The migration script preserves the original PG12 volume as
<project>_db_data_pg12_backup. Stop the v3 stack, drop the fresh PG18 volume, copy the backup volume back into the target volume name, and boot the v2.4.29 tag. Full recipe in the meta-repo doc. - Cold tarball replay. Same shape, but restores from
backups/iris_pg12_volume_<timestamp>.tar.gz. Slower, identical outcome. - Logical dump replay. If only the
pg_dumpall.sqlis available: deploy v2.4.29, let it initialise an empty cluster, drop the emptyiris_db, thenpsql -f iris_pg12_dump_<timestamp>.sql.
All three routes assume the old iriswebapp_* images are still cached locally (or still pullable from ghcr). Verify with docker images | grep iriswebapp before you need them.
Integrations sweep
This is the part of the migration most likely to bite, and it is best done before the maintenance window. Every external tool that talks to IRIS must be on /api/v2:
- Anything calling a legacy path — will start failing as soon as v3 is up. Grep your integrations for
/case/,/alerts/,/datastore/,/dim/,/global/, and/manage/calls that are not prefixed with/api/v2, and port them using the legacy endpoint migration map. - Ingestion webhooks (Splunk, Sentinel, XSOAR, etc.) — re-point any legacy path, then re-test. Session-based flows also now issue refresh tokens (v3's cookie format differs).
- The Python client — will be bumped to a v3-compatible release.
- API-key auth (
Authorization: Bearer <api_key>) — unchanged, no migration required.
Common issues
Alembic hangs on the alert-cluster backfill.
The migration walks existing alerts to place them into initial clusters. On instances with >1M alerts this takes minutes. Not stuck — check docker stats to see CPU on the app container. If it hits a wall-clock limit and gets killed, restart the app; Alembic resumes from the last committed step.
PostgreSQL rejects the app or worker password after the migration.
PG18 uses scram-sha-256 where PG12 used md5. The migration script re-hashes the POSTGRES_USER and POSTGRES_ADMIN_USER roles using the values in your .env, so the app + worker containers authenticate normally. Additional roles you provisioned manually must be re-hashed:
docker compose exec db psql -U "$POSTGRES_ADMIN_USER" -d iris_db \
-c "ALTER USER myuser WITH PASSWORD 'their-existing-password';"
Fresh docker compose up complains it cannot pull iris-backend:v3.0.0-beta.1.
Either the release has not yet published (check ghcr.io/dfir-iris/iris-backend/versions), or your registry client isn't authenticated to a private package. Retag or run docker login ghcr.io if the visibility hasn't yet been flipped to public.
Session-based clients get 401 after upgrade.
The refresh-token cookie format changed. Users need to log in once through the browser, or clients must call POST /api/v2/auth/login to obtain a v3 refresh token. API-key auth is unaffected.
Nginx fails to start with cannot load certificate ".../iris_dev_cert.pem".
v3 expects the certificate at certificates/web_certificates/iris_dev_cert.pem and the key at certificates/web_certificates/iris_dev_key.pem (or whatever CERT_FILENAME / KEY_FILENAME in .env point at). See configuration §Certificates.
After the migration
- Keep the pre-flight backups and the fresh mid-window backup for at least a week — long enough to observe latent issues in normal usage.
- Once confident, reclaim disk with
docker volume rm <project>_db_data_pg12_backupand delete thebackups/iris_pg12_*.tar.gzandiris_pg12_*.sqlartefacts. - Re-check that no integration is still calling a legacy path — those now fail rather than degrade, so a silent scheduled job can go unnoticed for days. The legacy endpoint migration map lists each legacy path alongside its
/api/v2replacement. - Enable the new subsystems that fit your team — war rooms, alert clusters, and investigation flows are the most-used additions.
Kubernetes deployments
The bundled Helm chart and EKS manifests (under iris-backend/deploy/) reference the pre-carve iriswebapp_db image. The same major-version jump applies — a PG18 pod will refuse to start against a PG12 PVC.
An automated migration for Kubernetes is not shipped in this release. The recommended approach:
- Scale the IRIS app/worker deployments to 0.
- Exec into the running PG12 pod and
pg_dumpallto a file on the PVC (or stream out viakubectl exec ... > dump.sql). - Take a volume snapshot of the PVC (cloud-provider feature) as a belt-and-braces backup.
- Delete the PG12 StatefulSet/Deployment and its PVC.
- Apply the v3.0.0-beta manifests so PG18 initialises a fresh PVC.
kubectl cpthe dump into the new PG18 pod andpsql -v ON_ERROR_STOP=1 -f dump.sql.- Scale the app/worker back up, then deploy the new
frontendcomponent and wire nginx to proxy non-/api/*traffic to it.
If you need a scripted version, open an issue — we will prioritise it based on demand.