Repository navigation
Expand file tree
/
Copy pathdocker-compose.yaml
More file actions
379 lines (366 loc) · 13.8 KB
/
Copy pathdocker-compose.yaml
File metadata and controls
379 lines (366 loc) · 13.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
---
networks:
localobserve:
driver: bridge
driver_opts:
com.docker.network.driver.mtu: 1500
# x-logging: cap Docker's json-file log growth on the host SSD. Without this,
# any service that logs to stdout (falco, otel-collector) grows without bound.
x-logging: &default-logging
driver: json-file
options:
max-size: "10m"
max-file: "3"
services:
# Falco normally runs as a HOST systemd service (falco-modern-bpf) whose JSON
# output lands in /var/log/falco/events.jsonl, which the otel-collector
# tails via the /var/log/falco-host bind mount (wire it up with
# `sudo bash scripts/install-falco-system.sh --config-only`). The container
# variant below stays fully functional but opt-in behind the `agents`
# profile so it never double-runs with a host install:
# docker compose --profile agents up -d falco
falco:
# Pinned to falcosecurity/falco:0.45.0 (September 2026) — 0 HIGH, 0 CRITICAL CVEs.
# Tracked in docs/container_security_scan.md.
image: falcosecurity/falco@sha256:788f1129c542171813083d4afc61b16730a47dde8c23d9c39370acef996349b6
profiles:
- agents
privileged: true
runtime: runc
mem_limit: 512m
memswap_limit: 512m
volumes:
# Uncomment the line below on bare Docker Engine (not Desktop) to enable
# container metadata enrichment in Falco alerts:
# - ${DOCKER_SOCKET:-/var/run/docker.sock}:/host/var/run/docker.sock
- /dev:/host/dev
- /proc:/host/proc:ro
- ./falco-config.yaml:/etc/falco/config.d/zz-local.yaml:ro
- ./falco_rules.local.yaml:/etc/falco/falco_rules.local.yaml:ro
# Threat-intel fragment regenerated by tools/sync_threat_intel.py
# (committed default in rules/threat_intel/ keeps falco bootable pre-sync).
- ./.data/threat-intel:/var/lib/threat-intel:ro,z
- ./.data/falco:/var/log/falco:z
- /etc:/host/etc:ro
pid: host
network_mode: host
logging: *default-logging
environment:
- FALCO_K8S_AUDIT_ENDPOINT=
- FALCO_HOSTNAME=localhost
healthcheck:
test: ["CMD", "falco", "--list", "rules"]
interval: 60s
timeout: 10s
retries: 2
start_period: 30s
# Like Falco, osquery normally runs as a HOST systemd service (osqueryd)
# writing results straight into ./.data/osquery via the `logger_path`
# option in /etc/osquery/osquery.conf — the collector tails that file. The
# container variant stays opt-in behind the same `agents` profile so it
# never double-runs with a host install:
# docker compose --profile agents up -d osquery
#
# The mounted osquery profile is selectable: set OSQUERY_CONFIG in .env to
# one of these bare filenames (compose prefixes ./ automatically):
# osqueryd.conf - balanced detection (default)
# osqueryd-ssd-optimized.conf - lowest disk/CPU impact (daily/hourly intervals)
# osqueryd-deep-forensic.conf - on-demand deep investigation
# See docs/optimization.md; measure profile deltas with tools/bench_stack.py.
osquery:
profiles:
- agents
# Pinned to Ubuntu 22.04 base (5.17.0) — avoids Ubuntu 20.04 libsystemd CVE-2021-33910
# tracked in docs/container_security_scan.md. Refresh via Trivy scan.
image: osquery/osquery:5.17.0-ubuntu22.04@sha256:47cb6ef2d2724c37c9db89f79387ad1d22051821823280459e439b9ac49d662a
privileged: true
runtime: runc
mem_limit: 256m
memswap_limit: 256m
pid: host
network_mode: host
restart: unless-stopped
volumes:
# Uncomment the line below on bare Docker Engine (not Desktop) to enable
# Docker container introspection tables (docker_containers, docker_images, docker_info):
# - /var/run/docker.sock:/var/run/docker.sock
- /:/host:ro
- ./osquery.flags:/etc/osquery/osquery.flags:ro
- ./${OSQUERY_CONFIG:-osqueryd.conf}:/etc/osquery/osquery.conf:ro
# Threat-intel feeds (FIM-monitored via file_paths.threat_intel)
- ./.data/threat-intel:/var/lib/threat-intel:ro,z
- ./.data/osquery:/var/log/osquery:z
command: [
"osqueryd",
"--flagfile=/etc/osquery/osquery.flags",
"--config_path=/etc/osquery/osquery.conf",
"--logger_path=/var/log/osquery",
"--database_path=/var/log/osquery/osquery.db"
]
logging: *default-logging
openobserve:
image: openobserve/openobserve@sha256:d4a878fac1f6c56003764f7f2a1625668917388f167e222c8c810de3f54c56ba
mem_limit: 1g
memswap_limit: 1g
environment:
- ZO_ROOT_USER_EMAIL=${ZO_ROOT_USER_EMAIL:-root@example.com}
- ZO_ROOT_USER_PASSWORD=${ZO_ROOT_USER_PASSWORD:-Complexpass#123}
- ZO_DATA_DIR=/data
- ZO_FEATURE_PER_THREAD_LOCK=true
- RUST_LOG=error
- O2_AI_ENABLED=true
- O2_TOOL_API_URL=http://openobserve:5080
- ZO_RETENTION_PERIOD=180
- ZO_MMDB_DISABLE_DOWNLOAD=true
- ZO_SKIP_SSRF_CHECKS=true
ports:
- "5080:5080"
- "5081:5081"
volumes:
- ./.data/openobserve-data:/data:z
logging: *default-logging
extra_hosts:
- "host.docker.internal:host-gateway"
restart: unless-stopped
networks:
- localobserve
healthcheck:
test: ["CMD", "/openobserve", "--version"]
interval: 30s
timeout: 5s
retries: 3
start_period: 15s
dcgm-exporter:
image: nvcr.io/nvidia/k8s/dcgm-exporter:latest
runtime: nvidia
profiles:
- gpu
mem_limit: 256m
memswap_limit: 256m
cap_add:
- SYS_ADMIN
ports:
- "9400:9400"
networks:
- localobserve
otel-collector:
image: otel/opentelemetry-collector-contrib@sha256:fd328de2552466ad78385e1b1289c3f2402b1c45f265b252aab1955b42845ac1
command: ["--config=/etc/otelcol-contrib/config.yaml"]
# Hard ceiling = 3x the memory_limiter soft limit (256 MiB) so the limiter
# can trigger graceful back-pressure before the OOM killer fires.
# Bumped from 512m → 768m after observing 255/512 MiB usage (~50%)
# under normal osquery + system_logs ingest load.
mem_limit: 768m
memswap_limit: 768m
environment:
- ZO_ROOT_USER_EMAIL=${ZO_ROOT_USER_EMAIL:-root@example.com}
- ZO_ROOT_USER_PASSWORD=${ZO_ROOT_USER_PASSWORD:-Complexpass#123}
volumes:
- ./otel-collector-config.yaml:/etc/otelcol-contrib/config.yaml:ro
- ./.data/osquery:/var/log/osquery:ro,z
- ./.data/falco:/var/log/falco:ro,z
# Host-systemd Falco JSON output (scripts/install-falco-system.sh); on
# hosts without a host Falco install this dir is simply empty.
- /var/log/falco:/var/log/falco-host:ro
# Shared threat-intel artifacts are available for optional collector
# enrichment as well as the Falco/osquery consumers.
- ./.data/threat-intel:/var/lib/threat-intel:ro,z
- ./.data/clamav:/var/log/clamav:ro,z
- ./.data/goflow2:/var/log/goflow2:ro,z
restart: unless-stopped
depends_on:
- openobserve
# rsigma must exist on the compose network before the collector starts,
# otherwise every early export fails DNS resolution and retries/drops.
- rsigma
ports:
- "4317:4317"
- "4318:4318"
networks:
- localobserve
logging: *default-logging
healthcheck:
test: ["CMD", "/otelcol-contrib", "--version"]
interval: 30s
timeout: 5s
retries: 3
start_period: 15s
clamav:
# Pinned to the 2026-10-04 current digest of clamav/clamav:latest — was a
# floating :latest whose drift silently introduced pcre2 CVE-2026-103111
# (issue #111). Tracked in docs/container_security_scan.md; re-pin via the
# weekly Container Image Scan workflow when upstream rebuilds with pcre2 >= 10.49-r0.
image: clamav/clamav:latest@sha256:7659dcb0db47941d3cf8336af84bbb63c7e70b76fc00601774358412b42ed186
restart: unless-stopped
profiles:
- scan
# ClamAV loads the full virus DB (~1.5GiB) into shared memory.
# A hard limit here would cause startup failure; we set a generous
# ceiling and rely on the DB's MaxFileSize/MaxScanSize caps for I/O safety.
mem_limit: 2g
memswap_limit: 2g
volumes:
- clamav_db:/var/lib/clamav
- clamav_sockets:/tmp
- /:/hostfs:ro
environment:
- CLAMAV_NO_CLAMD=false
- CLAMAV_NO_FRESHCLAMD=false
networks:
- localobserve
clamav-scanner:
# Same pin rationale as the clamav daemon above (issue #111).
image: clamav/clamav:latest_base@sha256:4bd758114dbe0964edf6742cd8ddd98ed73eb8fcd70ce8bb4f53b19a79f07fe1
restart: unless-stopped
profiles:
- scan
# Disable inherited server health check (this container only runs clamdscan client)
healthcheck:
disable: true
# Scanner client is lightweight; DB is in shared memory owned by clamav daemon.
mem_limit: 256m
memswap_limit: 256m
volumes:
- clamav_db:/var/lib/clamav:ro
- clamav_sockets:/tmp
- /:/hostfs:ro
- ./.data/clamav:/var/log/clamav:z
entrypoint: ["/busybox/sh", "-c"]
command:
- |
echo "LocalSocket /tmp/clamd.sock" > /tmp/clamd.conf
while true; do
# Wait for clamd to be ready
while ! clamdscan -c /tmp/clamd.conf --ping 10 2>/dev/null && ! echo PING | nc -w 1 -U /tmp/clamd.sock; do sleep 10; done
# Clean scan.log before starting a new scan
echo "Starting full filesystem scan..." > /var/log/clamav/scan.log
# Perform traversal with robust exclusions pruned at find-level for maximum speed and safety
find /hostfs \
\( -path "/hostfs/sys" -o \
-path "/hostfs/proc" -o \
-path "/hostfs/dev" -o \
-path "/hostfs/run" -o \
-path "/hostfs/tmp" -o \
-path "/hostfs/var/log" -o \
-path "/hostfs/var/lib/docker" -o \
-path "/hostfs/var/lib/containerd" \) -prune \
-o -type f -print0 2>/dev/null | xargs -0 clamdscan -c /tmp/clamd.conf --multiscan --fdpass \
--log=/var/log/clamav/scan.log 2>&1 | grep -vE 'Not supported file type|cli_realpath|LibClamAV Warning' || true
sleep 86400
done
networks:
- localobserve
depends_on:
- clamav
alert-receiver:
build:
context: ./alerts/webhook
dockerfile: Dockerfile
image: localobserve-webhook:latest
restart: unless-stopped
mem_limit: 64m
memswap_limit: 64m
ports:
- "9000:9000"
environment:
- DISPLAY=${DISPLAY:-:0}
- DBUS_SESSION_BUS_ADDRESS=${DBUS_SESSION_BUS_ADDRESS:-unix:path=/run/user/1000/bus}
- SLACK_WEBHOOK_URL=${SLACK_WEBHOOK_URL:-}
- EMAIL_TO=${EMAIL_TO:-}
- GENERIC_WEBHOOK_URL=${GENERIC_WEBHOOK_URL:-}
volumes:
- ./.data/alerts:/var/log/alerts:z
networks:
- localobserve
logging: *default-logging
healthcheck:
test: ["CMD", "wget", "--spider", "-q", "http://localhost:9000"]
interval: 30s
timeout: 5s
retries: 3
start_period: 10s
rsigma:
image: ghcr.io/timescale/rsigma:0.19.0
restart: unless-stopped
mem_limit: 128m
memswap_limit: 128m
working_dir: /app
logging: *default-logging
volumes:
- ./rules/sigma:/app/rules/sigma:ro
ports:
- "9090:9090"
command: ["engine", "daemon", "--config", "/app/rules/sigma/rsigma.yaml", "--allow-plaintext"]
networks:
- localobserve
healthcheck:
test:
- CMD
- /rsigma
- engine
- status
- --addr
- 127.0.0.1:9090
interval: 30s
timeout: 10s
retries: 3
start_period: 30s
# GoFlow2 is only useful when a NetFlow/sFlow exporter is pointed at this
# host. It is gated behind the `netflow` profile so a default `docker compose up`
# does not run it (idle CPU/RAM/disk cost + 30 upstream HIGH CVEs, see #84).
# Enable with: docker compose --profile netflow up -d goflow2
goflow2:
image: netsampler/goflow2:latest
restart: unless-stopped
profiles:
- netflow
user: "1000:1000"
mem_limit: 256m
memswap_limit: 256m
logging: *default-logging
ports:
- "2055:2055/udp"
- "6343:6343/udp"
volumes:
- ./.data/goflow2:/var/log/goflow2:z
command: ["-transport=file", "-transport.file=/var/log/goflow2/flows.jsonl"]
networks:
- localobserve
# Issue #57: on-demand container image / registry vulnerability scanner.
# Disabled by default; run with `--profile scan` (or the scan-equivalent in
# podman/nerdctl). Writes Grype JSON results into a shared volume that the
# OTEL collector can ingest and that `tools/parse_scanner_results.py` summarizes.
scanner:
# Pinned to the 2026-10-04 digest of :debug (0 HIGH / 0 CRITICAL verified via
# Trivy before pinning) — the same floating-tag drift trap closed for clamav
# in #111; closes the compose-side gap behind #85. CI now also scans this
# exact digest (container-image-scan matrix "grype-debug"), not just :latest.
# Refresh via the weekly scan + re-pin flow; see docs/container_security_scan.md.
image: anchore/grype:debug@sha256:1d10432f8ca74c12197a2e5aee75149ac0d2d9a0a26cd08c4018843a8145492f
restart: "no"
profiles:
- scan
mem_limit: 1g
memswap_limit: 1g
environment:
# Scan target: an image reference or a registry:// URL (set via task).
- SCAN_TARGET=${SCAN_TARGET:-alpine:3.10}
- GRYPE_DB_AUTO_UPDATE=${GRYPE_DB_AUTO_UPDATE:-true}
- PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/busybox:/
volumes:
- ./.data/scanner:/var/lib/scanner:z
entrypoint: ["/busybox/sh", "-c"]
command:
- |
set -euo pipefail
mkdir -p /var/lib/scanner
echo "Scanning $${SCAN_TARGET} ..."
grype "$${SCAN_TARGET}" -o json > /var/lib/scanner/scan.json || true
echo "Wrote /var/lib/scanner/scan.json"
# Keep the container around so results can be inspected / re-scanned.
sleep infinity
networks:
- localobserve
volumes:
clamav_db:
clamav_sockets: