Environment Variables
All configuration is driven by environment variables defined in the .env
file at the repository root. Copy .env.example to .env before starting.
Memory & Upload
Upload limits are derived from a single memory budget rather than set
directly. Set APP_MEMORY_MB and everything else scales automatically.
Variable |
Default |
Description |
|---|---|---|
|
|
Total RAM (in MB) allocated to the backend container — the default for
the enforced container memory limit and the budget for derived settings.
All derived values use the effective budget (the enforced cgroup limit
when one is set, else this value): JVM heap = 50%, max upload size = 16%
(at most 1/3 of the heap — the parser holds the whole capture in memory,
see |
|
|
Enforced backend container memory limit. Tracks |
|
|
Backend CPU limit. Analysis is subprocess-heavy and parallel, so the backend is sized above the other services. |
|
|
Postgres container limits. |
|
|
MinIO container limits. |
|
|
nginx container limits. Request bodies larger than
|
|
|
Keycloak container limits (auth overlays only). |
Feature Toggles
Every switch that turns a capability on or off, in one place. Each is also documented in its own section below with the surrounding detail.
Variable |
Default |
Effect |
|---|---|---|
|
|
Gates the API behind a Keycloak JWT and adds a login flow. Set to
|
|
|
Deployment-wide switch for intrusion-detection enrichment. |
|
|
Master switch for automatic deletion. |
|
|
Scheduled job that flips captures stranded in |
|
|
Turns geolocation enrichment off entirely — no lookups, no bundled-database
resolution, so captures analyzed while it is off get no geo fields.
Distinct from |
|
|
Suppresses the |
Note
These are deployment-wide. Several analysis features also have per-upload toggles in the UI (nDPI, Suricata, file extraction) which apply to a single capture. Where both exist the deployment-wide switch wins: turning Suricata off here skips it even for an upload that requested it.
Note
Two capabilities are switched by configuration rather than a boolean. AI
features are governed by LLM_API_BASE_URL — point it at a reachable local
inference server to enable them; the production overlays require it to be set
explicitly. Custom detection rules activate when a signatures file is present.
File Retention
Retention is entirely opt-out. To keep everything indefinitely — the usual choice for air-gapped or evidence-preservation deployments — set:
FILE_RETENTION_ENABLED=false
That is sufficient on its own, and it is the master switch: the cleanup
scheduler is not registered at all, so none of the settings below do anything
while it is false.
Danger
0 does not mean “never” for FILE_RETENTION_HOURS. It means delete
every analysis file immediately — an expiry cutoff of “now” — and the next
hourly sweep will remove them all.
0 means “never” only for MONITOR_FILE_RETENTION_HOURS and
PACKET_RETENTION_HOURS, which are explicitly guarded against zero. To
disable deletion, use FILE_RETENTION_ENABLED=false; never
FILE_RETENTION_HOURS=0.
Deletion is only ever triggered by these settings; nothing else in the application removes captures or packets on its own.
Variable |
Default |
Description |
|---|---|---|
|
|
Set to |
|
|
Number of hours after upload before an analysis file is automatically
deleted (only applies when |
|
|
Number of hours before a monitor-mode snapshot file is deleted;
Warning Setting a non-zero value here is not currently recommended.
Scheduled cleanup deletes the file directly, bypassing the snapshot
re-ordering and change-detection replay that removing a snapshot
through the UI performs, which leaves gaps in |
|
|
Number of hours after upload before a file’s raw packets are pruned,
while the file keeps its conversations and analysis results ( |
Analysis Queue & Reconciliation
Uploaded files are analyzed asynchronously by an in-memory thread pool. Analysis
is Suricata-dominated: for every capture Suricata processes it adds roughly
50 s of fixed overhead, because the full rule set is reloaded on each
invocation. Packet scanning on top of
that still scales with capture size, so total analysis time grows — but the 50 s
floor is paid even by a tiny capture. Throughput is CPU-bound. When the
pool and its queue are both full the executor applies back-pressure: the
upload request runs the analysis inline and slows down, rather than dropping the
file. A reconciliation job additionally flips any file left stuck in
PROCESSING past a timeout to FAILED (covering crashes and restarts, since
the queue is in-memory and lost on restart).
Sizing guidance: keep ASYNC_MAX_POOL_SIZE at or below the host CPU core
count to avoid CPU contention between concurrent Suricata runs. Raise
ASYNC_QUEUE_CAPACITY to absorb larger bursts before back-pressure kicks in.
For a 24-core box, ASYNC_MAX_POOL_SIZE=20 (leaving headroom for the JVM, DB,
and MinIO) with a queue of a few hundred is a reasonable starting point.
Variable |
Default |
Description |
|---|---|---|
|
|
Number of threads kept alive to run analyses. These stay up even when idle. |
|
|
Maximum analysis threads. Keep at or below the host CPU core count — analysis is CPU-bound (Suricata), so oversubscribing degrades throughput. |
|
|
Files that may wait in the in-memory queue once all threads are busy.
Once the queue is also full, uploads apply back-pressure (run analysis
inline). This queue is not persisted — pending work is lost on
restart and recovered as |
|
|
Set to |
|
|
Minutes a file may stay in |
|
|
Deployment-wide kill-switch for Suricata IDS enrichment. Set to
|
Nginx
Variable |
Default |
Description |
|---|---|---|
|
|
Host port on which nginx listens. Change if port 80 is already in use. |
Public Origin & Authentication
These variables apply only when running with the production overlay that enables OIDC/Keycloak authentication. See Authentication (OIDC / Keycloak) for the full walkthrough. They are ignored by the base stack.
Variable |
Default |
Description |
|---|---|---|
|
|
The exact origin you browse to (scheme + host + port). Pins Keycloak’s
token issuer and the backend’s issuer check, so the browser must load
the app via this same origin. Include the port only if non-standard
(e.g. |
|
|
Backend toggle. When |
|
(none) |
Keycloak bootstrap admin username (admin console is served same-origin
at |
|
(none) |
Keycloak bootstrap admin password. Required — the production overlays abort if unset. See Production Hardening. |
Note
KEYCLOAK_ISSUER_URI, KEYCLOAK_JWK_SET_URI, and the VITE_AUTH_*
build args are set automatically by docker-compose.prod.yml and derived
from PUBLIC_URL — you normally do not set them by hand.
Spring Profile & CORS
The active Spring profile selects dev vs. hardened behaviour. The two
*-prod.yml overlays set SPRING_PROFILES_ACTIVE=prod; the base stacks
run dev. See Production Hardening for the full profile
matrix.
Variable |
Default |
Description |
|---|---|---|
|
|
|
|
dev: localhost list / prod: (empty) |
Comma-separated allowed browser origins for |
|
|
Directory for the |
LLM
Variable |
Default |
Description |
|---|---|---|
|
|
Base URL of an OpenAI-compatible inference API. See LLM Setup. |
|
(empty) |
API key sent in the |
|
|
Model identifier passed in each API request. Must match a model loaded on your inference server. |
|
|
Sampling temperature (0.0–2.0). Lower values produce more deterministic output; higher values more creative output. |
|
|
Maximum number of tokens the LLM may generate per response. Controls response length only — not the context window. Increase if stories are cut off; decrease to save compute. Recommended 4000–8000. |
|
(auto) |
The context window size (in tokens) configured on your LLM server. Used
to detect prompt-too-large errors early. If unset, auto-detected from
the |
|
|
HTTP timeout in seconds for LLM API requests. Local models can be slow — increase if you get timeout errors. |
|
|
Whether Story mode’s investigation phase declares its query as a native
tool, so the server constrains generation to the real schema instead of
letting the model free-type JSON. |
Geolocation
External IP addresses are enriched with country, city and (online only) ASN.
Two independent switches govern this: GEO_ENRICHMENT_ENABLED decides
whether geolocation happens at all, and GEO_FORCE_OFFLINE decides where
the answer comes from. They are easy to confuse.
GEO_ENRICHMENT_ENABLED=false -> no geo data at all (no lookups of any kind)
GEO_FORCE_OFFLINE=true -> geo data from the bundled MMDB only, zero egress
GEO_FORCE_OFFLINE=false -> ipinfo.io when reachable, MMDB when not
For an air-gapped deployment you want GEO_FORCE_OFFLINE=true, not
GEO_ENRICHMENT_ENABLED=false — the MMDB lookup is local to the container and
never touches the network, so disabling enrichment outright loses the map, the
country columns and the geo-based scanning signals for no egress benefit. See
Offline / Air-Gapped Deployment.
Variable |
Default |
Description |
|---|---|---|
|
|
Master switch. |
|
|
Suppresses the |
|
(bundled) |
Override the location of the offline MMDB. Empty (the default) uses the
DB-IP Lite database bundled in the backend image at
A path that does not exist logs a warning and falls back to the
bundled copy. A path that exists but cannot be opened — corrupt file,
wrong format, or unreadable permissions on a bind mount — does not fall
back: it logs |
Results are cached in ip_geo_cache with the source that produced them, and
re-looked up after 30 days. Switching a deployment offline therefore does not
invalidate geo data already resolved via ipinfo.io.
Overview Applications
Variable |
Default |
Description |
|---|---|---|
|
|
Cap the number of detected applications shown in the overview. Set to
|
|
|
Maximum detected applications shown in the overview when
|
File Extraction
Tunes the limits applied when extracting embedded files from captures. When any limit is hit, a warning is shown on the Extracted Files tab. See File Extraction.
Variable |
Default |
Description |
|---|---|---|
|
|
Max files extracted from a single raw TCP/UDP stream. Guards against runaway extraction on streams with many magic-byte sequences. |
|
|
Max number of non-HTTP streams scanned for embedded files per PCAP. |
|
|
Max size (MB) of a single extracted file that will be stored. Larger files are detected but skipped (shown with a “Too large” badge). |
Report Generation
The PDF report request carries a base64-encoded PNG of the topology diagram
(force-directed and hierarchical layouts) as a single JSON string field. Jackson
caps any single JSON string token at 20 MB by default, purely as a memory-safety
guard against unbounded allocation from one request — a dense topology’s
captured diagram can exceed that, which fails the request with
String length (...) exceeds the maximum length (...) before it reaches the
controller.
There is no separate variable for this. Like the upload limit in
Memory & Upload, it is derived from APP_MEMORY_MB by
backend/docker-entrypoint.sh: 2.5% of the effective budget, clamped to
8-256 MB. At the default APP_MEMORY_MB=2048 that is ~51 MB — above
Jackson’s 20 MB default but still a small, heap-proportional ceiling rather
than a flat one, so it doesn’t threaten a small deployment’s heap the way a
fixed 50 MB default would. Raise APP_MEMORY_MB (see Memory & Upload) if
large/dense topology diagrams still fail to download. See
PDF Report Export.
Frontend (build-time)
VITE_* variables are baked in at build time, so changing them requires a
rebuild (docker compose up -d --build).
Variable |
Default |
Description |
|---|---|---|
|
|
Polygon fidelity of the world map. |
|
|
Comma-separated list of accepted upload extensions. |
|
|
Toggles the 500-conversation rendering cap in the Network Topology
Diagram. Set |
|
|
Version string rendered in the app footer (passed as the
|
Database Configuration (internal)
The following variables configure the PostgreSQL connection. They are set automatically by Docker Compose and generally do not need to be changed unless you are connecting to an external database.
Variable |
Description |
|---|---|
|
Database name |
|
Database user |
|
Database password — change this in production. The backend’s
|
MinIO Configuration (internal)
Variable |
Description |
|---|---|
|
MinIO admin username (default |
|
MinIO admin password (default |