# Reference production compose stack for the EDR server + MySQL. # Distinct from docker-compose.yml (dev MySQL only). Full setup + # secret-file generation documented in docker-compose.prod.README.md. # Boot shortcut: # # docker compose -f docker-compose.prod.yml up -d # # The server image is pulled from ghcr.io/rawtrail/rawtrail-server; a local # build uses the docker-compose.prod.build.yml override for smoke tests and # air-gapped sites. services: mysql: image: mysql:8.4.9@sha256:c36050afdca850f23cef85703f84c7531a5ae155a11b5ee1c60acb09937c4084 restart: unless-stopped environment: # MySQL 8.4 reads these _FILE variants natively. Avoids root password in # any compose env block or docker inspect output. MYSQL_ROOT_PASSWORD_FILE: /run/secrets/mysql_root MYSQL_DATABASE: edr volumes: - edr-mysql-data:/var/lib/mysql secrets: - mysql_root healthcheck: # --silent and redirecting stderr keeps healthcheck logs out of the main # service log. The 10s start_period lets InnoDB recover before we gate on # a successful ping. test: ["CMD-SHELL", "mysqladmin ping --silent 2>/dev/null || exit 1"] interval: 5s timeout: 5s retries: 20 start_period: 10s # ClickHouse event archive (ADR-0015): the durable event store the server writes ingested events to and reads them back # from. Reachable only on the private compose network (no host port), same posture as mysql. The default user is # passwordless; the DSN rides a docker secret so a password can be added later without editing this file. clickhouse: image: clickhouse/clickhouse-server:24.8@sha256:1ffa82edee000a42c09313bd9f1293d94c570aee74babc1b3ca9983a35fa597b restart: unless-stopped environment: CLICKHOUSE_DB: edr # Required for the default user to authenticate over the network with an empty password; without it the server's # ClickHouse ping fails with code 516 (AUTHENTICATION_FAILED). Mirrors the dev/demo clickhouse service. CLICKHOUSE_DEFAULT_ACCESS_MANAGEMENT: "1" ulimits: # ClickHouse opens many files; the stock 1024 soft limit trips "too many open files" under load. nofile: soft: 262144 hard: 262144 volumes: - edr-clickhouse-data:/var/lib/clickhouse healthcheck: test: ["CMD", "clickhouse-client", "--query", "SELECT 1"] interval: 5s timeout: 5s retries: 20 start_period: 10s server: # Set EDR_VERSION in .env to pin a release tag (recommended). `latest` is # fine for dev but unsafe for production because image digests can drift. # For a local source build instead of a pull, layer the build override: # docker compose -f docker-compose.prod.yml -f docker-compose.prod.build.yml up -d --build image: ghcr.io/rawtrail/rawtrail-server:${EDR_VERSION:-latest} restart: unless-stopped # Longer than every stage of a graceful stop put together: the drain (EDR_SHUTDOWN_DRAIN, # 30s), the in-flight deadline (15s), the two loop joins (10s + 5s) and the telemetry # flush (5s): 65s in all. Docker's default is 10s, which cuts the drain the load # balancer is watching and severs in-flight requests (#1127). test/arch keeps this # in step with those five constants. Raise it too if you raise EDR_SHUTDOWN_DRAIN. stop_grace_period: 90s depends_on: mysql: condition: service_healthy clickhouse: condition: service_healthy environment: # The MySQL DSN needs the runtime root password that MYSQL reads from # the same secret file. We consume it via *_FILE (see server/config/ # file_env.go) rather than interpolating so the password never lands in # `docker inspect` output. The env key is blank here so the *_FILE path # wins unconditionally; the server's fileBackedGetenv wrapper does the # rest. EDR_DSN: "" EDR_DSN_FILE: /run/secrets/edr_dsn EDR_ENROLL_SECRET_FILE: /run/secrets/enroll_secret # Deployment root secret (>=32 bytes). Required unconditionally: the # host-token HMAC pepper and other long-lived keys derive from it via HKDF # (server/config/config.go loadSecretKey), so the server refuses to boot # without it. Read from a docker-secret file so it never lands in # `docker inspect`. Changing it invalidates every enrolled host. EDR_SECRET_KEY_FILE: /run/secrets/secret_key # ClickHouse event archive DSN (ADR-0015), the event store the server refuses to boot without. Passwordless on the # private network; blank env so the *_FILE path wins and no value lands in `docker inspect`. EDR_CLICKHOUSE_DSN: "" EDR_CLICKHOUSE_DSN_FILE: /run/secrets/clickhouse_dsn # TLS cert paths are required unless EDR_TLS_TERMINATED_BY_PROXY=1 says a # proxy in front terminates TLS. Mount fullchain.pem + privkey.pem under # ./tls/ (or override EDR_TLS_DIR to point at your cert store). The server # refuses to boot when either path is empty or unreadable. EDR_TLS_CERT_FILE: ${EDR_TLS_CERT_FILE:-/tls/fullchain.pem} EDR_TLS_KEY_FILE: ${EDR_TLS_KEY_FILE:-/tls/privkey.pem} EDR_LOG_LEVEL: ${EDR_LOG_LEVEL:-info} EDR_LOG_FORMAT: "json" OTEL_EXPORTER_OTLP_ENDPOINT: ${OTEL_EXPORTER_OTLP_ENDPOINT:-} OTEL_SERVICE_NAME: rawtrail-server volumes: # Optional: mount a tls dir if fullchain.pem + privkey.pem exist. The # `:ro` flag + `tls:` default-empty env makes this safe when unused. - ${EDR_TLS_DIR:-./tls}:/tls:ro secrets: - enroll_secret - edr_dsn - secret_key - clickhouse_dsn ports: - "${EDR_LISTEN:-8088}:8088" # No server-side HEALTHCHECK: the distroless/static base has no shell # and no wget/curl, so `CMD-SHELL` healthchecks always fail. Nothing # depends_on the server here, so operator-side readiness checks # (curl http(s)://host:${EDR_LISTEN}/readyz) are the canonical path. # If we ever need container-level health, compile a tiny static # healthcheck binary into the image and invoke it via # `HEALTHCHECK ["CMD", "/usr/local/bin/healthcheck"]` (no shell). volumes: edr-mysql-data: edr-clickhouse-data: secrets: mysql_root: file: ./secrets/mysql_root enroll_secret: file: ./secrets/enroll_secret # edr_dsn is derived from mysql_root at `docker compose up` time via a # compose-secret hack: the operator writes it once. See README. edr_dsn: file: ./secrets/edr_dsn secret_key: file: ./secrets/secret_key # Fixed passwordless DSN pointing at the clickhouse service on the private # network. In a secret file for parity with edr_dsn (a ClickHouse password, # if added, would live here). See README. clickhouse_dsn: file: ./secrets/clickhouse_dsn