Ctrl K
ComunidadGuías…

docker-compose.prod.README.md

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
# RawTrail server: docker-compose quickstart

This stack runs the rawtrail-server + MySQL on a single host. It's the simplest way to stand up the server for a pilot deployment of 10-500 agents. For larger fleets, use this as the starting shape and split MySQL onto its own instance.

## One-time setup

```sh
# 1. Pin a released version. `latest` works for dev but is not safe for prod
#    because image digests drift silently.
echo 'EDR_VERSION=v0.9.0' > .env
echo 'OTEL_EXPORTER_OTLP_ENDPOINT=http://host.docker.internal:4317' >> .env

# 2. Generate the secret files. The MySQL root password + the full DSN that
#    embeds it must stay in sync. This is the one awkward edge of using
#    distroless (no shell in the server image, so we cannot derive the DSN
#    at runtime from the root-password secret). secret_key is the deployment
#    root secret (>=32 bytes); the server derives the host-token HMAC pepper and
#    other long-lived keys from it and refuses to boot without it. Changing it
#    later invalidates every enrolled host, so back it up.
mkdir -p secrets tls
# 0700 on the directory keeps the world-readable (0644) secret files below it
# unreadable to other local users: a 0700 dir blocks non-owners from traversing
# in, while the Docker daemon (root) still reads the files to bind-mount them.
chmod 0700 secrets
MYSQL_PASS=$(openssl rand -hex 24)
printf '%s' "$MYSQL_PASS" > secrets/mysql_root
printf 'root:%s@tcp(mysql:3306)/edr?parseTime=true&tls=false' "$MYSQL_PASS" > secrets/edr_dsn
printf '%s' "$(openssl rand -hex 32)" > secrets/secret_key
printf 'pilot-enroll-secret-rotate-me' > secrets/enroll_secret
# ClickHouse holds the event archive (ADR-0015). Its default user is passwordless on the private compose network, so this
# DSN is a fixed string; it lives in a secret file for parity with edr_dsn (add a ClickHouse password here later if you harden it).
printf 'clickhouse://default:@clickhouse:9000/edr' > secrets/clickhouse_dsn
# 0644, not 0600: Compose bind-mounts a file secret with the host file's owner
# and mode (the uid/gid/mode long-syntax options are Swarm only), and the server
# image runs as nonroot, so a 0600 file owned by your shell user is unreadable
# inside the container and the server crash-loops on "permission denied". The
# value still never lands in `docker inspect`, the process environment, or an
# image layer, which is the point of using a file secret.
chmod 0644 secrets/*

# 3. TLS. Put fullchain.pem + privkey.pem under ./tls (Let's Encrypt output via
#    certbot works directly). TLS is unconditionally required; the server
#    refuses to boot when either cert path is unreadable.

docker compose -f docker-compose.prod.yml up -d
```

## Verify

```sh
curl -sk https://localhost:8088/readyz | jq .
# {"status":"ok","checks":{"db":{"status":"ok","latency_ms":N}}}
```

`-k` bypasses cert verification for the local self-signed probe; production automation against a real Let's Encrypt-issued cert should drop it.

The first-boot break-glass redemption URL prints to stderr until the admin redeems it; capture with `docker compose -f docker-compose.prod.yml logs server | grep -B 1 -A 4 BREAK-GLASS`. Open the URL in a browser within the TTL (default 1h) to set a password and register a WebAuthn credential. See [`docs/install-server.md`](docs/install-server.md) for the full first-boot flow.

## Upgrade

```sh
# Edit .env to set the new EDR_VERSION.
docker compose -f docker-compose.prod.yml pull server
docker compose -f docker-compose.prod.yml up -d
```

No DB migration needed within the v0.1.x series; schema is `CREATE TABLE IF NOT EXISTS` throughout.

## Secret rotation

**Enroll secret**. Overwrite `secrets/enroll_secret` with the new value and restart the server so it re-reads the secret file:

```sh
docker compose -f docker-compose.prod.yml restart server
```

Existing per-host tokens are not affected by enroll-secret rotation because they were derived at enroll time, not re-verified against the secret on every auth. SIGHUP is NOT wired for secret reload; only TLS cert reload responds to it.

**MySQL root password**. Overwrite both `secrets/mysql_root` and `secrets/edr_dsn` with the new password, then `docker compose up -d --force-recreate mysql server`. The volume data persists across recreates.

**Deployment secret key** (`secrets/secret_key`). Treat this as un-rotatable in normal operation: every host token derives from it, so overwriting it invalidates every enrolled host and forces a fleet-wide re-enroll. Back it up rather than rotating it.

**TLS cert**. Drop new `tls/fullchain.pem` + `tls/privkey.pem`. The server watches SIGHUP for TLS reload; `docker compose kill -s HUP server` swaps the cert without dropping active connections.

## OTel metrics + logs

Set `OTEL_EXPORTER_OTLP_ENDPOINT` in `.env` to your SigNoz / collector endpoint. The server emits:

- Traces for every HTTP request + DB query.
- Logs via `otelslog` with `service.name=rawtrail-server`.
- Metrics: `edr.events.ingested`, `edr.alerts.created`, `edr.enrolled.hosts`, `edr.offline.hosts`, `edr.retention.processes.rows_deleted`, `edr.db.query.duration`, `edr.agent.queue.dropped`. See [docs/install-server.md](docs/install-server.md#otel-metrics-and-logs) for the full list and [docs/operations.md](docs/operations.md#metrics-and-monitoring) for what to alert on.

There is no Prometheus scrape endpoint.

## Known limits

- Single MySQL instance; no replicas. For pilots ≤500 agents this is fine.
- No horizontal scaling of the server instance yet. A second replica would need a shared enroll-secret rotation mechanism (currently each replica reads its own secret file) and a sticky session or shared cookie store. Tracked as future work for the post-MVP scaling story.
- No automatic backup of `edr-mysql-data`. Operators run their own `mysqldump` or volume snapshot schedule.