All configuration is done through environment variables. See the included .env.example below for the full, commented list. Three of them are required; everything else has a working default.
# Copy to .env and fill in real values: cp .env.example .env
# .env is gitignored and is read by both docker compose and the app container.
# --- Django ---------------------------------------------------------------
# Generate with:
# python -c 'from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())'
DJANGO_SECRET_KEY=replace-me
# "true" enables debug mode. Leave false for anything shared/deployed.
DJANGO_DEBUG=false
# Required when DJANGO_DEBUG=false. Comma-separated, no spaces.
# The compose service name `web` is added automatically (the agent, worker and
# beat containers reach the web service by that name); override with
# REEF_INTERNAL_WEB_HOST if you rename the service.
DJANGO_ALLOWED_HOSTS=localhost,127.0.0.1
# Any host/IP you open the UI on. Reaching it by LAN IP or hostname without
# adding it here gives a bare "Bad Request (400)" page.
# e.g. DJANGO_ALLOWED_HOSTS=localhost,127.0.0.1,192.168.1.50,reef.example.com
#
# CSRF trust for POST actions (login, acknowledge, run scan, ...). Leave BLANK
# and every concrete host above is trusted automatically on http+https, bare and
# :8000. Set it explicitly (scheme required) only to override that:
# e.g. DJANGO_CSRF_TRUSTED_ORIGINS=https://reef.example.com
DJANGO_CSRF_TRUSTED_ORIGINS=
# "true" when a TLS-terminating proxy / load balancer sits in front (it forwards
# plain http to the app). Makes Django trust X-Forwarded-Proto / -Host.
DJANGO_TRUST_PROXY=false
# --- Superuser -----------------------------------------------------------
# Created on first container start by entrypoint.sh (manage.py ensure_superuser).
DJANGO_SUPERUSER_USERNAME=admin
DJANGO_SUPERUSER_PASSWORD=replace-me
DJANGO_SUPERUSER_EMAIL=admin@example.com
# --- API keys ----------------------------------------------------------
# Accepted in the `X-API-Key` header by the token-protected API routes and by
# the agent. Comma-separated; supply several to allow rotation.
# Generate one with: python -c 'import secrets; print(secrets.token_urlsafe(32))'
DJANGO_API_KEYS=
# --- Agent -------------------------------------------------------------
# The agent authenticates with this key; it MUST be one of DJANGO_API_KEYS.
REEF_AGENT_API_KEY=
# Optional; defaults to the monitored host's /etc/hostname (stable across restarts).
REEF_AGENT_NAME=
# Seconds between job polls / heartbeats.
REEF_POLL_INTERVAL=15
# --- Reef behaviour --------------------------------------------------
# Findings at or above this severity raise a notification (info|low|medium|high|critical).
REEF_NOTIFY_MIN_SEVERITY=high
# An agent with no check-in for this long is marked offline.
REEF_AGENT_OFFLINE_AFTER_SECONDS=300
# When "true", the auditd scan loads Reef's audit.rules into the host kernel
# (auditctl -R). Leave "false" to only *report* which rules are missing. To also
# persist the ruleset across reboots, add a read-write mount of /etc/audit to the
# agent service in docker-compose.yml.
REEF_AUDIT_MANAGE=false
# Size cap for the agent's scan scratch tmpfs (syft/grype temp files + the
# intermediate SBOM the grype scan builds). RAM-backed: size it to what the host
# can spare. A scan that needs more space fails cleanly with ENOSPC rather than
# filling the host disk. Grype's vuln DB lives on the grype-db volume, not here.
REEF_SCAN_TMP_SIZE=2g
# --- Celery ---------------------------------------------------------
# docker-compose sets these; override here if pointing at an external Redis.
# CELERY_BROKER_URL=redis://redis:6379/0
# CELERY_RESULT_BACKEND=redis://redis:6379/1
# --- SQLite3 ------------------------------------------------------
# Path inside the container (mounted volume so the DB survives rebuilds).
SQLITE_PATH=/data/db.sqlite3
# --- AI interpretation (optional, opt-in) -----------------------------
# Adds an "Interpret" button to the scan detail page and finding modals that
# sends scan/finding data to a local Ollama model and shows back a plain-
# language summary. Off unless OLLAMA_ENABLED=true. Needs real memory/CPU to
# run acceptably -- leave this off on a small box.
#
# 1. Uncomment the four lines below.
# 2. Start the bundled Ollama service too:
# COMPOSE_PROFILES=ollama docker compose up -d
# (or add `COMPOSE_PROFILES=ollama` as its own line in this file). The
# `ollama-init` one-shot service pulls OLLAMA_MODEL on first start.
# OLLAMA_ENABLED=true
# OLLAMA_URL=http://ollama:11434
# OLLAMA_MODEL=llama3.2:3b
# OLLAMA_MEM_LIMIT=8g
You must set DJANGO_SECRET_KEY, DJANGO_API_KEYS, and REEF_AGENT_API_KEY before running anything beyond local dev.
Django & networking
| Variable |
Default |
Meaning |
DJANGO_DEBUG |
false |
true for local dev |
DJANGO_ALLOWED_HOSTS |
localhost,127.0.0.1 |
comma-separated; REEF_INTERNAL_WEB_HOST is always added |
REEF_INTERNAL_WEB_HOST |
web |
compose/k8s name of the web service — trusted in ALLOWED_HOSTS so agent/worker calls (Host: web) aren’t rejected |
DJANGO_CSRF_TRUSTED_ORIGINS |
derived from ALLOWED_HOSTS |
leave blank to auto-trust every concrete host; set explicitly to override |
DJANGO_TRUST_PROXY |
false |
true behind a TLS-terminating proxy — trusts X-Forwarded-Proto / -Host |
Authentication
| Variable |
Default |
Meaning |
DJANGO_API_KEYS |
— |
comma-separated; accepted in the X-API-Key header |
REEF_AGENT_API_KEY |
— |
the key the agent uses — must be one of DJANGO_API_KEYS |
DJANGO_SUPERUSER_* |
— |
credentials for the superuser created on first web start |
Agent identity & polling
| Variable |
Default |
Meaning |
REEF_AGENT_NAME |
container hostname |
agent identity |
REEF_SCAN_ROOT |
/host |
where the agent scans (compose mounts host / there) |
REEF_POLL_INTERVAL |
15 |
agent poll / heartbeat seconds |
Notifications & job/agent health
| Variable |
Default |
Meaning |
REEF_NOTIFY_MIN_SEVERITY |
high |
findings at or above this severity raise a notification |
REEF_AGENT_OFFLINE_AFTER_SECONDS |
300 |
silence before an agent is marked “offline” |
REEF_JOB_STALE_AFTER_SECONDS |
3600 |
a claimed/running job with no result becomes expired |
Metrics retention
| Variable |
Default |
Meaning |
REEF_METRIC_MIN_INTERVAL_SECONDS |
20 |
minimum gap between stored agent resource samples (per agent) |
REEF_METRIC_RETENTION_DAYS |
14 |
agent resource samples older than this are pruned hourly |
Scan tuning
| Variable |
Default |
Meaning |
REEF_FIM_DEFAULT_PATHS |
/etc,/bin,/sbin,/usr/bin,/usr/sbin,/boot |
FIM watch list |
REEF_AUDIT_MANAGE |
false |
true → the auditd scan loads Reef’s ruleset into the host kernel |
REEF_AUDIT_RULES_PATH |
<repo>/audit.rules |
ruleset the server diffs loaded rules against |
REEF_SCAN_TMP_SIZE |
2g |
size cap for the agent’s scan-scratch tmpfs (TMPDIR=/scan-tmp) |
REEF_RAW_OUTPUT_MAX_BYTES |
24000000 |
raw output larger than this is truncated for storage; DATA_UPLOAD_MAX_MEMORY_SIZE is derived from it |
REEF_GRYPE_SOFT_MAX_BYTES |
16000000 |
agent-side: trims descriptions/URLs/CVSS vectors if the trimmed Grype payload would still exceed this |
Storage & Celery
| Variable |
Default |
Meaning |
SQLITE_PATH |
/data/db.sqlite3 |
database file (mounted volume) |
GRYPE_DB_CACHE_DIR |
/var/cache/grype |
Grype’s vulnerability DB, persisted on the grype-db volume |
CELERY_BROKER_URL / CELERY_RESULT_BACKEND |
redis://redis:6379/0 / /1 |
set by compose |
DJANGO_EMAIL_BACKEND |
console |
notification email copies |
Optinal AI Integration
| Variable |
Default |
Meaning |
OLLAMA_ENABLED |
true |
whether or not web expects an AI endpoint to be enabled |
OLLAMA_URL |
http://ollama:11434 |
the api endpoint for your local ollama instance |
OLLAMA_MODEL |
llama3.2:3b |
which model you want to use to perform the interpretation |
OLLAMA_MEM_LIMIT |
8g |
RAM limit for the ollama container (only applies if using the in cluded one) |
Two gotchas worth knowing up front
- 400
DisallowedHost on every agent/worker call. The agent and worker
reach web as http://web:8000, which carries Host: web. Settings always
appends REEF_INTERNAL_WEB_HOST (default web) to ALLOWED_HOSTS —
without it, nothing ever checks in.
- Bare “Bad Request (400)” opening the UI on a LAN IP or hostname. Add that
host to
DJANGO_ALLOWED_HOSTS. Each concrete host there is then auto-added
to CSRF_TRUSTED_ORIGINS (http+https, bare and :8000), so POST actions —
login, acknowledge, run scan, mark-read — work without a second variable.
Behind a TLS-terminating proxy, also set DJANGO_TRUST_PROXY=true.
See Troubleshooting for more failure modes.