# TraceRoot Development Infrastructure # # Make sure to update the credential placeholders with your own secrets. # We mark them with # CHANGEME in the file below. # # All services are bound to localhost (127.0.0.1) to only accept connections # from the local machine for security. services: postgres: image: docker.io/postgres:${POSTGRES_VERSION:-17} restart: always environment: POSTGRES_USER: ${POSTGRES_USER:-postgres} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-postgres} # CHANGEME POSTGRES_DB: ${POSTGRES_DB:-postgres} TZ: UTC PGTZ: UTC ports: - 127.0.0.1:5432:5432 volumes: - postgres_data:/var/lib/postgresql/data # Schema managed by Prisma migrations (cd ui && npx prisma migrate dev) healthcheck: test: ["CMD-SHELL", "pg_isready -U postgres"] interval: 3s timeout: 3s retries: 10 clickhouse: image: docker.io/clickhouse/clickhouse-server:${CLICKHOUSE_VERSION:-25.2} restart: always environment: CLICKHOUSE_DB: ${CLICKHOUSE_DB:-default} CLICKHOUSE_USER: ${CLICKHOUSE_USER:-clickhouse} CLICKHOUSE_PASSWORD: ${CLICKHOUSE_PASSWORD:-clickhouse} # CHANGEME # Read by sql_gateway_bootstrap_user.xml via from_env, so the password for the # only access-management account is never written into a mounted config file. SQL_GATEWAY_BOOTSTRAP_PASSWORD: ${SQL_GATEWAY_BOOTSTRAP_PASSWORD:-dev-sql-gateway-bootstrap} ports: - 127.0.0.1:8123:8123 - 127.0.0.1:9000:9000 volumes: - clickhouse_data:/var/lib/clickhouse - clickhouse_logs:/var/log/clickhouse-server # Defines sql_gateway_bootstrap, the only account holding access management. # Kept off the application account, which rest/worker/billing/detector all use. - ./backend/db/clickhouse/bootstrap/sql_gateway_bootstrap_user.xml:/etc/clickhouse-server/users.d/zz-sql-gateway-bootstrap.xml:ro healthcheck: test: wget --no-verbose --tries=1 --spider http://localhost:8123/ping || exit 1 interval: 5s timeout: 5s retries: 10 start_period: 1s # One-shot, idempotent: provisions the SQL-gateway DB users. sql_gateway_writer # is the DEFINER of the views created by migration 012, so it MUST exist before # migrate-clickhouse runs. Uses clickhouse-client against the live server (not the # first-boot-only /docker-entrypoint-initdb.d), so it also provisions existing # data volumes. See backend/db/clickhouse/bootstrap/sql_gateway_users.sql. clickhouse-init: image: docker.io/clickhouse/clickhouse-server:${CLICKHOUSE_VERSION:-25.2} depends_on: clickhouse: condition: service_healthy volumes: - ./backend/db/clickhouse/bootstrap/sql_gateway_users.sql:/bootstrap/sql_gateway_users.sql:ro - ./backend/db/clickhouse/bootstrap/sql_gateway_readonly.sql:/bootstrap/sql_gateway_readonly.sql:ro # Credentials arrive as environment rather than command arguments, so they stay # out of the container's command line and out of `ps`. They are still readable # via `docker inspect` and the container config -- that is inherent to compose # environment, and anyone with daemon access is already root-equivalent here. # The $$ below are escaped for the shell rather than substituted by Compose. environment: # Provisioning runs as the bootstrap identity, not the application account: # CREATE USER and SET DEFINER need access management, which the account the app # runs as deliberately does not have. CLICKHOUSE_USER: sql_gateway_bootstrap CLICKHOUSE_PASSWORD: ${SQL_GATEWAY_BOOTSTRAP_PASSWORD:-dev-sql-gateway-bootstrap} # Dev stack: weak known defaults, matching this stack's other credentials. # Not the writer, though. Nothing logs in as it, since the curated views are # SQL SECURITY DEFINER and run on its grants, so a known password only creates # an account that can read the raw tables. Left empty, clickhouse-init below # generates one and never stores it. SQL_GATEWAY_WRITER_PASSWORD: ${SQL_GATEWAY_WRITER_PASSWORD:-} CLICKHOUSE_DATABASE: ${CLICKHOUSE_DATABASE:-default} CLICKHOUSE_RO_PASSWORD: ${CLICKHOUSE_RO_PASSWORD:-dev-sql-gateway-ro} entrypoint: > /bin/sh -c " set -e; case \"$$CLICKHOUSE_DATABASE\" in ''|[0-9]*|*[!A-Za-z0-9_]*) echo 'FATAL: CLICKHOUSE_DATABASE must be an unquoted ClickHouse identifier matching [A-Za-z_][A-Za-z0-9_]*' >&2; exit 1;; esac; if [ -z \"$$SQL_GATEWAY_WRITER_PASSWORD\" ]; then SQL_GATEWAY_WRITER_PASSWORD=$$(od -An -v -N32 -tx1 /dev/urandom | tr -d ' \n'); fi; W=$$(printf '%s' \"$$SQL_GATEWAY_WRITER_PASSWORD\" | sha256sum | cut -d' ' -f1); sed -e \"s/__WRITER_HASH__/$$W/g\" -e \"s/__DB__/$$CLICKHOUSE_DATABASE/g\" /bootstrap/sql_gateway_users.sql > /tmp/sql_gateway_users.sql; clickhouse-client --host clickhouse --port 9000 --user \"$$CLICKHOUSE_USER\" --queries-file /tmp/sql_gateway_users.sql; if [ -z \"$$CLICKHOUSE_RO_PASSWORD\" ]; then clickhouse-client --host clickhouse --port 9000 --user \"$$CLICKHOUSE_USER\" --query 'DROP USER IF EXISTS sql_gateway_ro'; echo 'clickhouse-init: CLICKHOUSE_RO_PASSWORD is unset, so the SQL gateway read-only account is not provisioned and any existing one was dropped'; else R=$$(printf '%s' \"$$CLICKHOUSE_RO_PASSWORD\" | sha256sum | cut -d' ' -f1); sed -e \"s/__RO_HASH__/$$R/g\" -e \"s/__DB__/$$CLICKHOUSE_DATABASE/g\" /bootstrap/sql_gateway_readonly.sql > /tmp/sql_gateway_readonly.sql; clickhouse-client --host clickhouse --port 9000 --user \"$$CLICKHOUSE_USER\" --queries-file /tmp/sql_gateway_readonly.sql; fi " restart: "no" minio: image: docker.io/minio/minio:latest restart: always command: server /data --console-address ":9001" environment: MINIO_ROOT_USER: ${MINIO_ROOT_USER:-minio} MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD:-minio-password} # CHANGEME ports: - 9090:9000 - 127.0.0.1:9091:9001 volumes: - minio_data:/data healthcheck: test: ["CMD", "mc", "ready", "local"] interval: 5s timeout: 5s retries: 5 start_period: 1s minio-init: image: docker.io/minio/mc:latest depends_on: minio: condition: service_healthy entrypoint: > /bin/sh -c " mc alias set myminio http://minio:9000 $${MINIO_ROOT_USER:-minio} $${MINIO_ROOT_PASSWORD:-minio-password}; mc mb myminio/traceroot --ignore-existing; exit 0; " redis: image: docker.io/redis:7-alpine restart: always command: redis-server --appendonly yes ports: - 127.0.0.1:6379:6379 volumes: - redis_data:/data healthcheck: test: ["CMD", "redis-cli", "ping"] interval: 3s timeout: 3s retries: 10 migrate-clickhouse: build: context: . dockerfile: docker/Dockerfile.migrate-clickhouse # The DSN is built inside the container rather than passed as an argument, for two # reasons each caught in review: # - a password in `command:`/`entrypoint:` is visible in the container's argv and # in `docker inspect`, which is the exposure moving clickhouse-init's # credentials into the environment had already removed; # - a password containing a URL-reserved character breaks DSN parsing outright # ("invalid port" after host). Percent-encoding every byte is valid userinfo # and survives any character; verified against goose with a password of # p/a#s?w&o=rd. environment: CLICKHOUSE_DATABASE: ${CLICKHOUSE_DATABASE:-default} SQL_GATEWAY_BOOTSTRAP_PASSWORD: ${SQL_GATEWAY_BOOTSTRAP_PASSWORD:-dev-sql-gateway-bootstrap} entrypoint: - /bin/sh - -c - | set -e PW=$$(printf '%s' "$$SQL_GATEWAY_BOOTSTRAP_PASSWORD" | od -An -v -tx1 | tr -d '\n ' | sed 's/../%&/g') # The DSN travels in the environment, never in argv. goose accepts the driver and # connection string as GOOSE_DRIVER/GOOSE_DBSTRING, so the expanded password stays # out of /proc//cmdline, which is world-readable, unlike /proc//environ. # Exported here rather than in the compose `environment:` block, because that would # put it back in `docker inspect` output. export GOOSE_DRIVER=clickhouse export GOOSE_DBSTRING="clickhouse://sql_gateway_bootstrap:$$PW@clickhouse:9000/$$CLICKHOUSE_DATABASE" exec goose -dir /migrations up depends_on: clickhouse: condition: service_healthy clickhouse-init: condition: service_completed_successfully restart: "no" volumes: postgres_data: driver: local clickhouse_data: driver: local clickhouse_logs: driver: local minio_data: driver: local redis_data: driver: local