DuckDB
The same single-container shape as the SQLite deployment, but with the telemetry store swapped for DuckDB's columnar engine. The main database (users, organizations, projects, notification rules) stays SQLite; endpoints, spans, exceptions, metrics, and logs go into a DuckDB file next to it. No external Postgres or ClickHouse required.
Pick this over the plain SQLite image when your dashboards slow down as data grows: on the same box, DuckDB keeps the dashboard responsive at 10 to 100 times more stored telemetry rows, and columnar compression makes the telemetry file several times smaller for the same data. See Hardware Requirements for sizing guidance and benchmark numbers.
Two operational differences from the SQLite image:
- The image is Debian, not Alpine. The DuckDB engine links glibc static libraries, so this build cannot run on musl-based images.
- Set
DUCKDB_MEMORY_LIMITon memory-capped hosts. DuckDB sizes its memory budget to roughly 80% of what it thinks is host RAM, and inside a container it sees the host, not the container limit. An uncapped DuckDB in a capped container is the one configuration that reliably gets the backend OOM-killed under ingest.
Quick Start
Pull the pre-built image and run it with a persistent data volume:
docker pull ghcr.io/tracewayapp/traceway:duckdb
docker run -d --name traceway \
-p 80:80 \
-v "$(pwd)/traceway-data:/data" \
-e JWT_SECRET="your-jwt-secret-min-32-characters-long" \
-e APP_BASE_URL="https://traceway.example.com" \
-e DUCKDB_MEMORY_LIMIT="2GB" \
ghcr.io/tracewayapp/traceway:duckdbSet DUCKDB_MEMORY_LIMIT to roughly half the memory you give the container. On an uncapped dedicated host you can omit it and let DuckDB tune itself.
After starting, open http://localhost/register to create your first account.
Build from Source
Only needed if you want to run a custom or modified build:
docker build -f Dockerfile.duckdb -t traceway:duckdb .Then use traceway:duckdb instead of ghcr.io/tracewayapp/traceway:duckdb in the commands above. The build needs CGO, so building outside Docker requires CGO_ENABLED=1 go build -tags telemetry_duckdb ./cmd/traceway on a glibc system; see Build Tags.
Blob storage
Identical to the SQLite image: blobs (source maps, session recordings, AI traces) go to /data/storage by default, or to S3 with STORAGE_TYPE=s3 plus the S3_* variables. See the SQLite page for the S3 example; every flag works the same here.
Configuration
All SQLite deployment variables apply unchanged (JWT_SECRET, APP_BASE_URL, SQLITE_PATH, STORAGE_*, S3_*, PORTS, SQLITE_RETENTION_DAYS, SESSION_RECORDING_RETENTION_DAYS). The retention worker prunes the DuckDB telemetry tables on the same SQLITE_RETENTION_DAYS schedule and issues a CHECKPOINT afterwards so deleted rows are actually reclaimed from disk.
On top of those, the DuckDB engine has three knobs:
| Variable | Default | Description |
|---|---|---|
DUCKDB_MEMORY_LIMIT | (unset, DuckDB auto-tunes to ~80% of host RAM) | Memory budget for the telemetry engine, e.g. 2GB. Set it explicitly in any memory-capped container, to roughly half the container's RAM. |
DUCKDB_THREADS | (unset, DuckDB uses all cores) | Caps the engine's thread count, e.g. 4. Useful on shared hosts. |
DUCKDB_CHECKPOINT_THRESHOLD | (unset, DuckDB default 16MB) | WAL size that triggers a checkpoint, e.g. 256MB. Raise it under sustained ingest to reduce checkpoint stalls; the cost is a larger WAL on disk and a longer replay after a restart. |
Persistence
Mount a host folder or a named volume at /data to keep everything across restarts:
/data/
├── traceway.db # users, organizations, projects, ... (SQLite)
├── traceway.db-wal # SQLite WAL
├── traceway.db-shm # SQLite shared memory
├── traceway_telemetry.duckdb # endpoints, exceptions, spans, metrics, ... (DuckDB)
├── traceway_telemetry.duckdb.wal # DuckDB WAL
└── storage/ # source maps, session recordings, AI traces (when STORAGE_TYPE=local)Backup tip: the main database supports a hot backup with sqlite3 traceway.db ".backup '/path/to/backup.db'". For the telemetry file, stop the container and copy traceway_telemetry.duckdb together with its .wal file; the WAL is required for crash recovery.
Switching from the SQLite image
The two images share the same main database file, so users, projects, tokens, and settings carry over when you point the DuckDB image at an existing /data volume. Telemetry does not: the DuckDB image starts a fresh traceway_telemetry.duckdb, and the rows in the old traceway_telemetry.db are not migrated. Old telemetry ages out of view and the old file can be deleted once you no longer need it. Switching back to the SQLite image behaves the same way in reverse.
Access Points
| URL | Description |
|---|---|
http://localhost/ | Frontend dashboard |
http://localhost/api/* | Backend API |
http://localhost/health | Health check |
http://localhost/api/health/deep | Telemetry store introspection (authenticated): dropped rows, insert failures, db/WAL size, engine memory |
Useful Commands
# View logs
docker logs traceway
docker logs -f traceway
# Enter container shell
docker exec -it traceway bash
# Health check
curl http://localhost/health
# Stop and remove (data survives in the mounted volume)
docker stop traceway && docker rm traceway