Self Host
Docker Compose

Docker Compose

Runs Traceway, ClickHouse, and PostgreSQL as separate services using the Minimal Container image.

Quick Start

No build required. Use the pre-built image from GitHub Container Registry with this docker-compose.yml:

services:
  traceway:
    image: ghcr.io/tracewayapp/traceway:minimal
    ports:
      - "80:80"
    environment:
      CLICKHOUSE_SERVER: "clickhouse:9000"
      CLICKHOUSE_DATABASE: "traceway"
      CLICKHOUSE_USERNAME: "default"
      CLICKHOUSE_PASSWORD: clickhouse
      CLICKHOUSE_TLS: "false"
      POSTGRES_HOST: "postgres"
      POSTGRES_PORT: "5432"
      POSTGRES_DATABASE: "traceway"
      POSTGRES_USERNAME: "traceway"
      POSTGRES_PASSWORD: traceway
      POSTGRES_SSLMODE: "disable"
      JWT_SECRET: change-this-to-a-secure-secret-at-least-32-chars
    volumes:
      - traceway-storage:/app/storage
    depends_on:
      clickhouse:
        condition: service_healthy
      postgres:
        condition: service_healthy
    restart: unless-stopped
 
  clickhouse:
    image: clickhouse/clickhouse-server:24.8-alpine
    environment:
      CLICKHOUSE_DB: traceway
      CLICKHOUSE_PASSWORD: clickhouse
    volumes:
      - clickhouse-data:/var/lib/clickhouse
    healthcheck:
      test: ["CMD", "clickhouse-client", "--password", "clickhouse", "--query", "SELECT 1"]
      interval: 5s
      timeout: 3s
      start_period: 10s
      retries: 10
    restart: unless-stopped
 
  postgres:
    image: postgres:17
    environment:
      POSTGRES_USER: traceway
      POSTGRES_PASSWORD: traceway
      POSTGRES_DB: traceway
    volumes:
      - postgres-data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U traceway"]
      interval: 5s
      timeout: 3s
      start_period: 10s
      retries: 10
    restart: unless-stopped
 
volumes:
  clickhouse-data:
  postgres-data:
  traceway-storage:
docker compose up -d

After starting, open http://localhost/register to create your first account.

The traceway-storage volume holds blob storage: uploaded source maps, session recordings, and AI traces. Without it those files are lost when the container is recreated. To use S3 instead of a volume, see Blob Storage.

The docker-compose.yml in the repository root builds from source instead. Use that if you are actively developing Traceway or need to run a modified build (docker compose up --build).

Configuration

Edit the environment variables in your compose file as needed:

VariableDefaultDescription
CLICKHOUSE_SERVERclickhouse:9000ClickHouse host:port
CLICKHOUSE_DATABASEtracewayClickHouse database name
CLICKHOUSE_USERNAMEdefaultClickHouse username
CLICKHOUSE_PASSWORDclickhouseClickHouse password
CLICKHOUSE_TLSfalseEnable TLS for ClickHouse
POSTGRES_HOSTpostgresPostgreSQL host
POSTGRES_PORT5432PostgreSQL port
POSTGRES_DATABASEtracewayPostgreSQL database name
POSTGRES_USERNAMEtracewayPostgreSQL username
POSTGRES_PASSWORDtracewayPostgreSQL password
POSTGRES_SSLMODEdisablePostgreSQL SSL mode
JWT_SECRETchange-this-to-a-secure-secret-at-least-32-charsSigns all authentication tokens (dashboard sessions and CLI/MCP device logins). Replace the placeholder with a strong, unique secret of at least 32 chars (openssl rand -hex 32) and keep it stable. See CLI Authentication.
APP_BASE_URL(none)Public URL of your Traceway instance (e.g. https://traceway.example.com). Used for SDK setup instructions, email links, deep links in notifications, OAuth callback URLs, and the CLI/MCP OAuth issuer plus device-login verification URL. If unset, the device-auth and /.well-known endpoints derive it per-request from the Host / X-Forwarded-* headers; set it explicitly behind a proxy that rewrites Host. See CLI Authentication.
TRUSTED_PROXIES(loopback)Comma-separated CIDRs of the proxies in front of Traceway whose X-Forwarded-For / X-Real-IP is trusted for the real client IP. Every per-IP rate limit keys on it, and /api/report stores it as each session's client.ip. Unset trusts loopback only, so a proxy container on a Docker network or an in-cluster ingress has to be listed before its header counts. A value replaces the default, so list every hop between the client and this server, not just the outermost one: naming only a CDN leaves a private ingress untrusted and turns X-Forwarded-For off entirely. * trusts every peer and lets clients spoof their address. Until the proxy is listed, every visitor shares one rate-limit bucket keyed on the proxy, and the server logs a warning the first time it sees a forwarding header from a peer it does not trust.
TRUSTED_PROXY_HEADER(none)A header taken as the client IP on every request, ahead of TRUSTED_PROXIES: X-Real-IP behind ingress-nginx, CF-Connecting-IP behind Cloudflare. Only safe when every request provably passes through a proxy that overwrites the header; a directly reachable backend lets clients set it freely.
REPORT_MAX_BODY_MB64Cap on the decompressed /api/report and /api/profiles/ingest body in megabytes. Gzip does not raise it. A body over the cap answers 413.
INGEST_MAX_CONCURRENT(2 x CPU cores, min 4)Telemetry ingest requests processed at once (/api/report, /api/profiles/ingest, /api/otel/*). Excess requests wait up to INGEST_ADMISSION_WAIT_SECONDS (default 5) and then get 503 with Retry-After; past a bounded waiting room (four times the capacity, at least 16) they are turned away immediately. Each admitted request can hold a decoded body, so on a memory-capped container size this from the memory limit rather than the CPU count.
UPLOAD_MAX_CONCURRENT4Source map and symbol uploads processed at once. Each holds a whole file in memory while it is parsed. Excess uploads wait 30 seconds and then get 503. Separate from the ingest pool so a CI burst cannot starve telemetry.
STORAGE_TYPElocalBlob storage backend for source maps, session recordings, and AI traces: local or s3. See Blob Storage for the S3 variables.
STORAGE_PATH./storageFolder for local blob storage, /app/storage in the minimal image. Mount a volume there. Ignored when STORAGE_TYPE=s3.
SESSION_RECORDING_RETENTION_DAYS30Days to keep session recording files on disk under STORAGE_PATH/recordings/. A worker deletes older files hourly and on startup. No effect when STORAGE_TYPE=s3. Set to 0 to disable.

ClickHouse table TTLs (metric_points, log_records, etc.) are managed at the schema level; this stack does not run the SQLite retention worker.

To run several backend containers against this stack, see Running more than one instance: the minimal image is built for that shape, and the databases in this compose file are what the instances share.

SSO (optional)

Adds Continue with Google / Continue with GitHub buttons to the login and register pages. See the SSO guide for the full provider setup walkthrough.

When configuring providers, set the callback URL on the provider side to <APP_BASE_URL>/api/auth/callback/{google|github}.

VariableDefaultDescription
GOOGLE_CLIENT_ID(unset)Google OAuth client ID. Setting both Google variables enables the Google button.
GOOGLE_CLIENT_SECRET(unset)Google OAuth client secret.
GITHUB_CLIENT_ID(unset)GitHub OAuth App client ID. Setting both GitHub variables enables the GitHub button.
GITHUB_CLIENT_SECRET(unset)GitHub OAuth App client secret.
OAUTH_SESSION_SECRET(falls back to JWT_SECRET)Cookie signing secret for the OAuth round-trip. Override to rotate independently of JWT_SECRET.

Access Points

URLDescription
http://localhost/Frontend dashboard
http://localhost/api/*Backend API
http://localhost/healthHealth check

Useful Commands

# View logs for all services
docker compose logs -f
 
# View logs for a specific service
docker compose logs -f traceway
 
# Stop all services
docker compose down
 
# Stop and remove all data
docker compose down -v