-
-
Notifications
You must be signed in to change notification settings - Fork 0
Getting Started
IRIS-NG is the Community Edition — LGPL-3.0, free, and self-hosted. There is no registration, activation, or license key at any point in this guide, and no feature is gated behind one.
Running it on a cloud host instead of your laptop? See Kubernetes for the Helm chart and DigitalOcean notes — though a single Droplet running the compose stack below is the faster and better-tested way to evaluate IRIS-NG.
- Docker 24+ and Docker Compose v2
- 8 GB RAM minimum (16 GB recommended when running local LLM via LM Studio)
- Git
git clone https://github.com/zach115th/iris-ng.git
cd iris-ng
# Generate the self-signed dev certs — REQUIRED, the stack will not start without them
bash scripts/generate_dev_certs.sh
# Copy and edit the environment file
cp .env.model .env
# Edit .env — set POSTGRES_PASSWORD, SECRET_KEY, IRIS_ADM_PASSWORD at minimum
docker compose -f docker-compose.dev.yml up -d --buildThe cert step is not optional. nginx, app, and worker all bind-mount
./certificates/read-only. If those files don't exist, nginx exits withcannot load certificate key ...and crash-loops, and the app/worker file mounts fail.
One-shot alternative. bash scripts/iris_helper.sh --init does everything above in a
single command: generates the certs if missing, writes a .env from .env.model with
random secrets and a generated admin password, then builds and starts the stack. It
prints the generated admin password at the end.
The stack comes up on https://localhost (nginx with a self-signed cert).
Default admin credentials are whatever you set in .env.
| Container | Role |
|---|---|
iriswebapp_app |
Flask/gunicorn web + REST API |
iriswebapp_worker |
Celery default queue (hooks, modules) |
iriswebapp_ai_worker |
Celery ai_queue (AI jobs, concurrency=1) |
iriswebapp_db |
PostgreSQL 17 |
iriswebapp_nginx |
TLS termination + reverse proxy |
iriswebapp_rabbitmq |
RabbitMQ broker |
IRIS-NG ships with an AI backend admin UI at /manage/settings → AI tab.
The default backend is LM Studio (always-on, runs locally). Configure the
primary slot URL to http://host.docker.internal:1234/v1 and the model name to
whatever you have loaded.
The two-slot design lets you configure an alt backend (any OpenAI-compatible endpoint — OpenAI, Azure OpenAI, a self-hosted gateway) and switch the active slot with no restart. See AI Features.
IRIS-NG is purely additive over v2.5.0-beta.1 (new tables and columns only — no renames, no removals), but vanilla DFIR-IRIS cannot connect to an iris-ng database without the schema additions in place.
scripts/import_vanilla_db.sh handles the full migration in two phases — one
command on the old host, one command on the new host.
Run this from the root of your vanilla DFIR-IRIS checkout on the source server:
bash scripts/import_vanilla_db.sh export --out ./iris-export--out is optional; if omitted the script auto-names the directory
./iris-export-YYYYMMDD-HHMMSS. The export creates a directory (not a
single file) containing up to five files:
iris-export/
├── iris.dump ← Postgres dump (required — migration aborts without this)
├── server_data.tar.gz ← uploaded evidence files and datastore
├── user_templates.tar.gz ← uploaded .docx report templates
├── iris-downloads.tar.gz ← previously generated reports
└── secrets.env ← IRIS_SECRET_KEY + IRIS_SECURITY_PASSWORD_SALT
Important: copy the entire directory (all files) to the new host. If you only copy
iris.dump, the script will still run but user passwords and uploaded files will not carry over.
Run this from the root of your iris-ng checkout on the destination server.
The --from flag is required and must point to the directory you copied over:
bash scripts/import_vanilla_db.sh import --from /path/to/iris-exportFor example, if you copied the bundle to /root/iris-ng/iris-export-20260629-184838:
bash scripts/import_vanilla_db.sh import --from /root/iris-ng/iris-export-20260629-184838The script will print a migration plan and ask for confirmation before wiping the existing iris-ng database. It then:
- Stops the app, worker, and ai_worker containers (leaves db running)
- Drops and recreates
iris_db, restores the Postgres dump - Runs a pre-Alembic schema sanity check
- Restores the named volumes (evidence, templates, generated reports)
- Carries
IRIS_SECRET_KEY+IRIS_SECURITY_PASSWORD_SALTinto iris-ng's.env - Reconciles any columns that vanilla's broken
alembic/env.pyleft uncommitted - Brings the app back up — Alembic adds iris-ng's new tables on startup
- Runs a post-Alembic check confirming all iris-ng tables exist
| Flag | What it does |
|---|---|
--out <dir> |
(export) write bundle to this directory (default: auto-named) |
--from <dir> |
(import) required — path to the exported bundle directory |
--skip-volumes |
(import) do not restore evidence/template volumes |
--skip-secrets |
(import) do not carry over IRIS_SECRET_KEY / IRIS_SECURITY_PASSWORD_SALT |
-f / --force
|
skip the confirmation prompt |
- Login with your existing credentials at
https://localhost/login - If secrets were not carried over, find the generated admin password with:
docker compose logs app | grep "Administrator password" - Reconfigure MISP sync under
/manage/modules— the old upstreamiris_misp_moduleconfig does not carry over (iris-ng uses a different module,iris_misp_sync_module)
The script supports vanilla DFIR-IRIS v2.4.x and v2.5.0-beta.1.
Symptom: migration completes without errors but no account can log in.
The most common cause is IRIS_SECURITY_PASSWORD_SALT not matching the value that was used to hash passwords in the source database. When the salt is wrong, every password verification fails regardless of whether the password is correct.
Diagnose:
diff \
<(grep -E 'IRIS_SECRET_KEY|IRIS_SECURITY_PASSWORD_SALT' /path/to/bundle/secrets.env) \
<(grep -E 'IRIS_SECRET_KEY|IRIS_SECURITY_PASSWORD_SALT' .env)No output = secrets match (not this issue). Any diff = secrets were not applied.
Fix — apply the source secrets manually:
python3 - /path/to/bundle/secrets.env /path/to/iris-ng/.env <<'EOF'
import sys, re
secrets_file, env_file = sys.argv[1], sys.argv[2]
with open(secrets_file) as f:
pairs = {l.split('=',1)[0]: l.rstrip() for l in f if l.strip() and not l.startswith('#')}
with open(env_file) as f:
content = f.read()
for key, line in pairs.items():
pat = re.compile(f'^{re.escape(key)}=.*$', re.MULTILINE)
content = pat.sub(line, content) if pat.search(content) else content + '\n' + line
with open(env_file, 'w') as f:
f.write(content)
print("Done")
EOFThen restart the stack so the new env vars take effect — docker restart alone is not sufficient:
docker compose -f docker-compose.dev.yml up -d --force-recreate --no-deps app worker ai_workerAlways use --build --force-recreate together:
git pull
docker compose -f docker-compose.dev.yml up -d --build --force-recreateDo not omit --force-recreate. A plain up --build can leave worker
and ai_worker on the old container while only app is rebuilt, causing ORM
mapper state mismatches that crash hook tasks with a NotImplementedError deep
in task_hook_wrapper before any module's hooks_handler runs.
- Navigate to
https://localhost/login - Sign in with the admin credentials from
.env - Go to
/manage/modules→ enable IrisMISPSync if you have a MISP instance - Go to
/manage/settings→ AI tab → enter your LM Studio (or cloud) backend URL
| Port | Service |
|---|---|
443 |
IRIS-NG web UI and API (nginx, TLS) |
The dev stack serves HTTPS with a self-signed certificate that you generate on the
host before first start (bash scripts/generate_dev_certs.sh) — it is not
generated inside the container. The script writes:
-
certificates/web_certificates/iris_dev_cert.pem+iris_dev_key.pem(nginx TLS) -
certificates/rootCA/irisRootCACert.pem(placeholder root CA)
docker-compose.dev.yml bind-mounts ./certificates/ into nginx (and the app/worker)
read-only. The browser will warn about the self-signed cert on first visit — accept
it (Advanced → Proceed). For production, replace these with certs from your real CA.
nginx runs as www-data (UID 33) and reads the bind-mounted key. If the key was created
by another tool with mode 0600 (root-only), nginx cannot read it and crash-loops with
cannot load certificate key "/www/certs/iris_dev_key.pem" ... Permission denied.
generate_dev_certs.sh deliberately chmod 644s the key to avoid this. If you generated
certs another way, make the key world-readable (chmod 644 certificates/web_certificates/iris_dev_key.pem)
and restart nginx.