Query with Quack
Run the duckdb-otlp Docker image in local-ducklake mode, enable the image’s Quack server, and connect to it from a DuckDB process outside Docker.
Use this setup when you want the container to handle ingestion and DuckLake writes while a local DuckDB shell, notebook, or script queries telemetry through Quack.
Quack is experimental in DuckDB v1.5.3. Run it on trusted local networks. For remote access, put it behind a TLS-terminating reverse proxy.
Configure
Section titled “Configure”Create .env:
DUCKDB_MODE=local-ducklakeDUCKDB_OTLP_TOKEN=dev-token-123456
DUCKLAKE_NAME=lake
DUCKDB_QUACK_ENABLED=1DUCKDB_QUACK_ADDR=0.0.0.0:9494DUCKDB_QUACK_TOKEN=dev-quack-token-123456DUCKDB_OTLP_TOKEN protects OTLP/HTTP ingest on port 4318. DUCKDB_QUACK_TOKEN protects DuckDB query access on port 9494.
Local DuckLake mode uses DUCKLAKE_CATALOG_PATH=/data/ducklake/catalog.duckdb and DUCKLAKE_DATA_PATH=/data/ducklake/storage by default. Set those variables when you want DuckLake metadata or Parquet files somewhere else inside the mounted /data volume.
Start the server
Section titled “Start the server”mkdir -p data
docker run --rm --name duckdb-otlp \ --env-file .env \ -p 4318:4318 \ -p 9494:9494 \ -v "$(pwd)/data:/data" \ ghcr.io/smithclay/duckdb-otlp:latestThe container initializes DuckLake, starts the OTLP/HTTP ingest server, and starts Quack in the same DuckDB process. The server names the catalog lake, so telemetry lands in tables such as lake.main.otlp_logs and lake.main.otlp_traces.
Leave the container running while clients send OTLP/HTTP requests and local DuckDB clients query through Quack.
POST a log record
In another terminal:
curl -sS http://localhost:4318/v1/logs \ -H 'Authorization: Bearer dev-token-123456' \ -H 'Content-Type: application/json' \ -d '{"resourceLogs":[{"resource":{"attributes":[{"key":"service.name","value":{"stringValue":"quack-local-ducklake-demo"}},{"key":"deployment.environment","value":{"stringValue":"docs"}}]},"scopeLogs":[{"scope":{"name":"duckdb-otlp-guide"},"logRecords":[{"timeUnixNano":"1704067200000000000","observedTimeUnixNano":"1704067200123456789","severityNumber":9,"severityText":"INFO","body":{"stringValue":"hello from local DuckDB over Quack"},"attributes":[{"key":"guide","value":{"stringValue":"query-with-quack"}}]}]}]}]}'Response:
{"status":"buffered","rows":1,"batches":1}Rows are accepted before they are durable. They commit automatically in the background, on graceful shutdown, or immediately after an explicit flush.
Connect from local DuckDB
Section titled “Connect from local DuckDB”In another terminal, start the DuckDB CLI on your host machine:
duckdbInstall and load Quack in the local DuckDB process:
INSTALL quack;LOAD quack;Create a Quack secret scoped to the container endpoint, then attach the remote DuckDB process:
CREATE SECRET duckdb_otlp_quack ( TYPE quack, SCOPE 'quack:localhost:9494', TOKEN 'dev-quack-token-123456');
ATTACH 'quack:localhost:9494' AS otel_writer (TYPE quack);For localhost, Quack uses plain HTTP by default. The Docker port mapping forwards local port 9494 to quack:0.0.0.0:9494 inside the container.
If you connect to Quack on a remote host, protect the transport with TLS termination. A tunneling extension such as Query-farm/quackscale keeps the Quack listener off the public network.
Query committed rows
Section titled “Query committed rows”The server accepts rows before it makes them durable. For a short local test, flush the ingest buffer from the remote DuckDB process first:
FROM otel_writer.query( 'SELECT * FROM otlp_flush(''otlp:0.0.0.0:4318'')');Then query the DuckLake tables from the same remote process:
FROM otel_writer.query( $$ SELECT time_unix_nano, service_name, severity_text, body FROM lake.main.otlp_logs WHERE service_name = 'quack-local-ducklake-demo' ORDER BY time_unix_nano DESC LIMIT 5 $$);Use the same pattern for traces:
FROM otel_writer.query( $$ SELECT trace_id, name, service_name, duration_time_unix_nano FROM lake.main.otlp_traces ORDER BY start_time_unix_nano DESC LIMIT 20 $$);Quack runs the query inside the container’s DuckDB process, so the query sees the attached lake catalog without a second process opening the DuckLake catalog files.
Run one-shot queries
Section titled “Run one-shot queries”For one-off queries, call quack_query:
FROM quack_query( 'quack:localhost:9494', $$ SELECT service_name, count(*) AS rows FROM lake.main.otlp_logs GROUP BY service_name ORDER BY rows DESC $$, token = 'dev-quack-token-123456');Use ATTACH when you want to reuse the connection across multiple queries. Use quack_query for scripts and quick checks.
Stop cleanly
docker stop duckdb-otlp
The image sends otlp_stop('otlp:0.0.0.0:4318') during shutdown,
so remaining buffered rows are committed before the process exits.
Clean up
Section titled “Clean up”After you stop the container, remove the local DuckLake files if you no longer need them:
rm -rf data