Skip to content

Migrating to IRIS v3

IRIS v3 is a major release. Compared with v2.4.x it changes:

  • Deployment layout — iris-web becomes a slim meta-repository with two git submodules (iris-backend and iris-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 from iriswebapp_* to iris-* 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 app container.
  • 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/v2 is 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.

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

  1. 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_data volume is free on the host.
  2. 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.
  3. Update the source tree — git checkout v3.0.0-beta.1 on iris-web, then git submodule update --init --recursive.
  4. Migrate .env — v3 introduces several new required variables (see the configuration reference and the meta-repo doc's §3.2b). A recycled v2 .env will boot with wrong defaults or fail.
  5. Migrate TLS material — copy or regenerate the cert into certificates/web_certificates/. See configuration §Certificates.
  6. 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.
  7. Bring up v3 — docker compose up -d. The app container runs Alembic on first boot.
  8. 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:

  1. 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.
  2. Cold tarball replay. Same shape, but restores from backups/iris_pg12_volume_<timestamp>.tar.gz. Slower, identical outcome.
  3. Logical dump replay. If only the pg_dumpall .sql is available: deploy v2.4.29, let it initialise an empty cluster, drop the empty iris_db, then psql -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_backup and delete the backups/iris_pg12_*.tar.gz and iris_pg12_*.sql artefacts.
  • 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/v2 replacement.
  • 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:

  1. Scale the IRIS app/worker deployments to 0.
  2. Exec into the running PG12 pod and pg_dumpall to a file on the PVC (or stream out via kubectl exec ... > dump.sql).
  3. Take a volume snapshot of the PVC (cloud-provider feature) as a belt-and-braces backup.
  4. Delete the PG12 StatefulSet/Deployment and its PVC.
  5. Apply the v3.0.0-beta manifests so PG18 initialises a fresh PVC.
  6. kubectl cp the dump into the new PG18 pod and psql -v ON_ERROR_STOP=1 -f dump.sql.
  7. Scale the app/worker back up, then deploy the new frontend component 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.