All-in-One Container
Single Docker container with ClickHouse, PostgreSQL, and the Go backend managed by supervisord. Best for simple setups where you want everything in one place.
Because both databases live inside the container, this image runs as a single instance. To run several backend instances, point the minimal image at shared databases.
Quick Start
Pull the pre-built image from GitHub Container Registry:
docker pull ghcr.io/tracewayapp/traceway:latest
docker run -d --name traceway \
-p 80:80 \
-v traceway-ch:/var/lib/clickhouse \
-v traceway-pg:/var/lib/postgresql/data \
-v traceway-storage:/app/storage \
ghcr.io/tracewayapp/traceway:latestAfter 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.
Build from Source
Only needed if you want to run a custom or modified build:
docker build -t traceway:latest .
docker run -d --name traceway \
-p 80:80 \
-v traceway-ch:/var/lib/clickhouse \
-v traceway-pg:/var/lib/postgresql/data \
-v traceway-storage:/app/storage \
traceway:latestEnvironment Variables
All environment variables have working defaults, no configuration required. Override as needed:
Set JWT_SECRET before exposing this image to anyone else. It signs every authentication token (dashboard sessions and CLI/MCP device logins), so the built-in default, which is public, lets anyone forge a token for any account. Generate a strong secret with openssl rand -hex 32. See CLI Authentication.
| Variable | Default | Description |
|---|---|---|
CLICKHOUSE_SERVER | localhost:9000 | ClickHouse host:port |
CLICKHOUSE_DATABASE | traceway | ClickHouse database name |
CLICKHOUSE_USERNAME | default | ClickHouse username |
CLICKHOUSE_PASSWORD | (empty) | ClickHouse password |
CLICKHOUSE_TLS | false | Enable TLS for ClickHouse |
POSTGRES_HOST | localhost | PostgreSQL host |
POSTGRES_PORT | 5432 | PostgreSQL port |
POSTGRES_DATABASE | traceway | PostgreSQL database name |
POSTGRES_USERNAME | traceway | PostgreSQL username |
POSTGRES_PASSWORD | (empty) | PostgreSQL password |
POSTGRES_SSLMODE | disable | PostgreSQL SSL mode |
JWT_SECRET | (built-in default, override in production) | Signs all authentication tokens: dashboard sessions and CLI/MCP device logins. The built-in default is public; set a strong unique secret (min 32 chars, openssl rand -hex 32) and keep it stable. See CLI Authentication. |
GIN_MODE | release | Gin framework mode |
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_MB | 64 | Cap 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_CONCURRENT | 4 | Source 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_TYPE | local | Blob storage backend for source maps, session recordings, and AI traces: local or s3. See Blob Storage for the S3 variables. |
STORAGE_PATH | ./storage | Folder for local blob storage, /app/storage in this image. Mount a volume there. Ignored when STORAGE_TYPE=s3. |
SESSION_RECORDING_RETENTION_DAYS | 30 | Days to keep session recording files on disk under STORAGE_PATH/recordings/. Files older than the TTL are deleted hourly and on startup. No effect when STORAGE_TYPE=s3. Set to 0 to disable. |
ClickHouse data retention is controlled per-table at the schema level (TTLs on
metric_points,metric_points_1m,metric_points_1h,log_records); other telemetry tables are retained indefinitely until you drop monthly partitions manually. TheSQLITE_RETENTION_DAYSsetting only applies in SQLite mode and has no effect in this image.
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}.
| Variable | Default | Description |
|---|---|---|
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
| URL | Description |
|---|---|
http://localhost/ | Frontend dashboard |
http://localhost/api/* | Backend API |
http://localhost/health | Health check |
Useful Commands
# View logs
docker logs traceway
docker logs -f traceway
# Enter container shell
docker exec -it traceway bash
# Check process status
docker exec traceway supervisorctl status
# Health check
curl http://localhost/health
# Stop and remove
docker stop traceway && docker rm traceway