Reef is five services, with an optional AI-powered 6th: one privileged agent per monitored host, and a Django
app (web, worker, beat) that shares a single image but runs a different
role per container.
The services
- web: gunicorn. Serves the dashboard (server-rendered shells + vanilla
fetch) and every JSON API route. Enqueues scan jobs. - worker:
celery -A app worker. Parses raw scan output intoFindingrows and runs the notification rules. - beat:
celery -A app beat. Every 60 seconds: turns dueScanSchedules into jobs (unless globally paused — see the toolbar play/pause button), expires abandoned jobs, and flags agents that stopped checking in. Hourly: it prunes old agent resource samples. - redis: the Celery broker and result backend.
- agent: its own image. No database access, no application code. It authenticates with a key from
DJANGO_API_KEYS, self-identifies by hostname, runs the scan tools against the host mounted read-only at/host, and only ever sends raw tool output back. All parsing is server-side, inapp/scanning/parsers/. - ollama: an optional AI-powered LLM endpoint that integrates seamlessly within scan and finding report enabling you to easily “interpret” the results.
web, worker, and beat are the same image (portigniter-reef:YYYY-MM-DD) started with a different role. See Management Commands for the entrypoint table. They share the SQLite database file on a Docker volume; WAL mode plus a 30-second busy timeout keep concurrent readers and writers from tripping “database is locked”.
Why SQLite instead of Postgres
Reef is deliberately lean for v1 — one shared SQLite file over a Docker volume, with WAL mode for concurrent access, is enough for a single-box or small-fleet deployment and keeps the stack to five containers with no separate database service to run or back up. PostgreSQL support is on the Roadmap for when that stops being true.
Where the trust boundary sits
The agent is the only component that touches the host directly, and it’s intentionally kept dumb: it has no database credentials and no Django code, so compromising it doesn’t hand over anything beyond enabling one to “run the bundled scan tools and read /host”. Every interpretation of scan output, deciding what’s a finding, what severity it is, whether it’s a known-exploited CVE etc. happens on web/worker, not on the privileged agent.