Self-hosted service
dfir-iris/iris-web avatar
dfir-iris/iris-web

IRIS v3 splits into three repos and one version knob

Collaborative Incident Response platform

1,599 stars334 forksShellLGPL-3.0

At a glance

What is it?
IRIS is a collaborative incident response platform, and from v3 the repository you clone is a meta repo that owns the Docker Compose stack and pins the rest through git submodules and pre-built images. The Compose file rolls four images together with a single IRIS_VERSION variable, and the maintainers say plainly that v3.0.0-beta.1 is not for production.
Who is it for?
Adopt IRIS if your response team needs a shared case platform with accounts, notes and a job queue on infrastructure you control, since the v2.4.29 stable line is the supported production path and the compose stack is the only deployment method documented for v3. Do not adopt the v3 beta for anything load bearing, because the maintainers state it is not recommended for production and the Kubernetes manifests carried over from v2 are explicitly marked unusable on it.
Can I use it commercially?
Yes, with conditions. LGPL-3.0 is a weak copyleft licence: you can use it inside commercial and closed-source software, but if you distribute changes to its own files, you must publish those changes under the same licence.
Is it still maintained?
Yes. The repository last received commits 18 days ago.
What is it written in?
Mainly Shell, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 20, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The repo you clone owns the stack, not the application

Starting with v3, IRIS ships as three coordinated repositories.

iris-web is this one, described as the meta or umbrella repository. It owns the Docker Compose stack, the top level docs and release orchestration. iris-backend is a Python and Flask API with workers. iris-frontend is a SvelteKit user interface.

The backend and frontend are wired into iris-web as git submodules. The consequence is stated plainly: `docker compose up` pulls pre-built images from `ghcr.io/dfir-iris/iris-{backend,db,nginx,frontend}`, so a pull-only deployment needs no build step and no submodule initialisation.

That is a real change in what you get when you clone. If you are evaluating this as a codebase, you are evaluating an orchestration repository with two pointers. If you are deploying it, you never touch the submodules at all.

To build locally instead of pulling, the two steps are submodule initialisation and the dev script:

bash
git submodule update --init --recursive
./scripts/dev-up.sh

The README describes `scripts/dev-up.sh` as equivalent to `docker compose -f docker-compose.yml -f docker-compose.build.yml up -d --build`, so the local build path is a compose overlay rather than a separate configuration.

Contributor facing documentation moved into the submodules alongside the code it describes, which is a good sign for a project that has outgrown one repository: `iris-backend/CODESTYLE.md`, `iris-backend/CONFIGURATION.md` which is described as a superset of the online docs, `iris-backend/architecture.md` covering service architecture and data flow, and `docs/upgrade-to-3.0.0.md` for the v2.4.x to v3 procedure.

Six containers, and RabbitMQ is not optional

The stack runs six services, and the compose file is explicit about each one.

The `app` service is the Flask API and web server, built from the `iris-backend` image, launched with `./iris-entrypoint.sh iriswebapp`. The `worker` service is the Celery job handler and it uses the same `iris-backend` image, which is a standard pattern: one image, two roles.

The `db` service is PostgreSQL from the `iris-db` image. The `rabbitmq` service is the broker for Celery, running `rabbitmq:3-management-alpine`. The `nginx` service terminates TLS and proxies, from the `iris-nginx` image. The `frontend` service is SvelteKit server side rendering from the `iris-frontend` image.

Both `app` and `worker` declare `depends_on` with `condition: service_healthy` for `db` and `rabbitmq`, so the API does not start against a database that is still initialising. Both services have a healthcheck with a 60 second `start_period`, which is long because a fresh PostgreSQL volume takes a while to come up.

RabbitMQ being mandatory is worth noting for anyone planning a small deployment. A background job queue is not an optional extra here; there is no single process mode described.

The compose file is described in its own header as the pull-only production default, with `docker-compose.build.yml` as the overlay for building from submodules.

One variable rolls four images together

The `.env.example` header states the design rule: every image tag is pinned by IRIS_VERSION so the four services roll together, and every `__MUST_BE_CHANGED__` value must be set.

The single knob is:

bash
IRIS_VERSION=v3.0.0-beta.1

and it is updated by `scripts/bump-version.sh`. The compose file uses it as the default tag for `iris-db` and `iris-backend`, in the form `ghcr.io/dfir-iris/iris-db:${IRIS_VERSION:-v3.0.0-beta.1}`, and the comment says it also covers `iris-nginx` and `iris-frontend`.

That last point is why it matters. With four independently tagged images, a partial upgrade is the default failure mode: a frontend expecting a field the backend stopped returning. One variable removes the possibility.

The hostname handling is the other piece of real design in this file. `IRIS_HOSTNAME` is described as the single source of truth, providing fallback defaults for `SERVER_NAME` in nginx, `PUBLIC_EXTERNAL_API_URL` for SvelteKit and `VITE_ALLOWED_HOSTS` for Vite development, with explicit values still taking precedence for split hostname deployments where the API is on a different host than the UI.

What it deliberately does not do is derive `ORIGIN` from `IRIS_HOSTNAME`. The comment explains why: the frontend reads `x-forwarded-host` per request instead, using `HOST_HEADER` and `PROTOCOL_HEADER` in the compose file, so the same container can serve the app under multiple hostnames without breaking SvelteKit's CSRF check.

There is a separate variable for browser origins, comma or whitespace separated, listing every origin a browser may load the instance from. It is read by both tiers: the app uses it as the CORS allow list and for the Socket.IO handshake check, and the first entry is the canonical public URL used for server side links such as notification emails; the frontend uses it as an origin guard that rejects POST, PUT, PATCH and DELETE from origins outside the list.

The admin password is printed once, and you can pre-set it

Getting past the first run takes a certificate, an environment file and one password.

Copy the template and edit it:

bash
cp .env.example .env

The README names five settings to set at minimum: `POSTGRES_PASSWORD`, `POSTGRES_ADMIN_PASSWORD`, `IRIS_SECRET_KEY`, `IRIS_SECURITY_PASSWORD_SALT` and `IRIS_HOSTNAME`.

TLS is not optional in the compose setup, because nginx terminates it and the instance is served at `https://<IRIS_HOSTNAME>`, defaulting to `https://localhost` on port 443. You supply a certificate and key at `certificates/web_certificates/iris_dev_cert.pem` and `iris_dev_key.pem`, or set `CERT_FILENAME` and `KEY_FILENAME` in `.env`. The README gives a self-signed pair for a quick start:

bash
openssl req -x509 -newkey rsa:2048 -sha256 -days 365 -nodes \
    -keyout certificates/web_certificates/iris_dev_key.pem \
    -out certificates/web_certificates/iris_dev_cert.pem \
    -subj "/CN=iris.local" \
    -addext "subjectAltName=DNS:localhost,IP:127.0.0.1"

Then `docker compose up -d`.

The first administrator account is created on first start and its password printed to stdout once. The README gives you the exact string to search for in the `iris_app` container logs, which is a `WARNING :: post_init :: create_safe_admin :: >>>` line, and also tells you to pre-set it with `IRIS_ADM_PASSWORD` in `.env` instead if you prefer.

That pre-set path is the one to use in any automated deployment, because a password printed to a log is a password that ends up in someone's terminal history or log aggregator.

The PG18 PGDATA comment is a real deployment gotcha

One comment in the compose file encodes a version specific workaround that is worth reading before you touch the database service.

The `db` service sets `PGDATA=/var/lib/postgresql/data/pgdata`, and the comment explains why: PostgreSQL 18's Docker image refuses to use a directory mounted directly as PGDATA, because it expects the version-prefixed layout that `pg_ctlcluster` produces. Pointing PGDATA at a sub path inside the mounted volume sidesteps that check.

This is the kind of detail that costs an afternoon if you do not know it is there. The symptom would be a database container that exits immediately with no useful error, and the fix is a one line environment variable.

The healthcheck is `pg_isready -U postgres -d iris_db`, so it is checking a specific user and database rather than the server generically. The volume is a named `db_data` mounted at `/var/lib/postgresql/data`.

The five PostgreSQL variables are passed through rather than hardcoded: `POSTGRES_USER`, `POSTGRES_PASSWORD`, `POSTGRES_ADMIN_USER`, `POSTGRES_ADMIN_PASSWORD` and `POSTGRES_DB`. So the container's superuser and the application user are configured separately, which is the correct arrangement and worth keeping.

Configuration beyond `.env` has two other sources. The README lists three options for settings and credentials, Azure Key Vault, environment variables and configuration files, and states that this is also the order of priority: if a setting is not set it falls back on the next option. That silent fallback is the reason to be deliberate about which source is complete, because a half filled `.env` will quietly fall through to the file based settings.

The Kubernetes manifests are labelled v2 and unusable on v3

The most operationally significant line in the repository layout section is about the Helm chart and EKS manifests.

`iris-backend/deploy/` holds a Helm chart and EKS manifests, and the README marks them v2-era and not usable for v3.0.0-beta.1. Docker Compose is the only supported deployment path for the beta, and the reader is pointed to section 5 of `docs/upgrade-to-3.0.0.md`, which covers what Kubernetes operators should do instead.

That is a deliberate marker rather than an oversight: the directory is still there because the code lives in the submodule, and the warning is there because someone will find it. If you are an infrastructure team with an existing Helm based pipeline, that section 5 is the paragraph to read before anything else.

Versioning policy is clear and worth internalising. Since 2.0.0, IRIS follows Semantic Versioning 2.0. The code ready for production is always tagged with a version number, and alpha and beta versions are explicitly not production ready. The master branch is not for production.

The recent tags reflect that: v2.4.29 from 2026-08-10 is the current production line, with v2.4.27 and v2.4.26 both from 2026-01-27, while v3.0.0-beta.1 is the newest thing in the repository and is the one the compose defaults point at.

So the repository's default configuration and its production recommendation disagree, on purpose. That is the single fact to hold onto.

Where a case management tool, or a shared notes system, fits instead

The comparison with a dedicated case management platform is about the forensics side. IRIS is built around investigations: notes, timelines, evidence and the artefacts an analyst collects, with the collaboration between responders as the stated purpose. A generic ticketing tool stores a description and an assignee. The content model is what differs, and it is the difference worth paying for.

The comparison with a shared wiki or notes system is about structure. Notes in a notebook are chronological and unqueryable. Notes in an incident platform are attached to a case, ordered in time, and visible to everyone working the same incident, which is the specific problem the README names when it describes helping responders share technical details during investigations.

Against rolling your own on a Flask and PostgreSQL stack, the honest count of what you would rebuild is the account system, the case model, the timeline, the attachment handling, the Celery job queue for long running analysis, the audit trail, and the permissions model. For a team that does this rarely, buying or adopting is easier. For a team that already has an identity provider and an audit requirement, the integration work may exceed the feature work.

The boundary the project itself draws is the important one. The Considerations section says Iris is still at an early stage, that it can already be used in production, and then says to set backups of the database and do not expose the interface on the internet, with a strong recommendation to use a private dedicated and secured network.

That is a network position, not a feature list. Any deployment plan that puts IRIS on a public network is ignoring the project's own advice, and the HTTP and WebSocket surface it exposes makes that advice sensible rather than cautious.

Editorial conclusion

Adopt IRIS if your response team needs a shared case platform with accounts, notes and a job queue on infrastructure you control, since the v2.4.29 stable line is the supported production path and the compose stack is the only deployment method documented for v3. Do not adopt the v3 beta for anything load bearing, because the maintainers state it is not recommended for production and the Kubernetes manifests carried over from v2 are explicitly marked unusable on it. Verify first that you can follow the README's own advice not to expose the interface to the internet, since it is meant for a private secured network, and decide between Azure Key Vault, environment variables and configuration files before you deploy, because the fallback order means a half filled .env silently takes the wrong source.

Frequently asked questions

How do I run IRIS?

Clone with `git clone --recursive https://github.com/dfir-iris/iris-web.git`, copy `.env.example` to `.env` and set at least `POSTGRES_PASSWORD`, `POSTGRES_ADMIN_PASSWORD`, `IRIS_SECRET_KEY`, `IRIS_SECURITY_PASSWORD_SALT` and `IRIS_HOSTNAME`, provide a TLS cert and key under `certificates/web_certificates/`, then run `docker compose up -d`. It serves at `https://<IRIS_HOSTNAME>`, default port 443.

Where is the administrator password?

The first administrator account is created on first start and its password is printed to stdout once. Search the `iris_app` container logs for the `WARNING :: post_init :: create_safe_admin :: >>>` line, or pre-set the password with `IRIS_ADM_PASSWORD` in `.env`.

Can I deploy IRIS to Kubernetes?

Not on v3.0.0-beta.1. The Helm chart and EKS manifests in `iris-backend/deploy/` are marked v2-era and not usable for the v3 beta, and Docker Compose is the only supported deployment path for it. Section 5 of `docs/upgrade-to-3.0.0.md` covers what Kubernetes operators should do.

Should I expose IRIS to the internet?

No. The Considerations section says to set backups of the database and not to expose the interface on the internet, with a strong recommendation to use a private dedicated and secured network.

How do I upgrade IRIS?

Read the release notes. Most migrations are handled automatically, but some changes may require manual work depending on the version. The procedure for v2.4.x to v3 is in `docs/upgrade-to-3.0.0.md`, and image tags for all four images are pinned together by the single `IRIS_VERSION` variable in `.env`.

What licence is IRIS released under?

LGPL-3.0, with the licence text in `LICENSE.txt` at the repository root.

Official sources

  1. dfir-iris/iris-web on GitHub
  2. Issues
  3. License: LGPL-3.0
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/dfir-iris-iris-web.svg)](https://hysenlabs.com/projects/dfir-iris-iris-web)

Was this useful?