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 stable as of DuckDB 2.0. These examples use its CONNECT statement. The container binds Quack to 0.0.0.0, and on any non-loopback bind Quack serves TLS, with a self-signed certificate unless you supply one. Clients pin that certificate by its fingerprint.
Configure
Section titled “Configure”Create .env:
DUCKDB_MODE=local-ducklakeDUCKDB_OTLP_TOKEN=dev-token-123456
DUCKDB_CATALOG=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.
Quack generates its self-signed certificate on first start and keeps it on the data volume, under /data/.duckdb/extension_data/quack. A redeployed container that mounts the same /data volume keeps the same fingerprint, so pinned clients keep working. A fresh volume gets a new certificate. To use a certificate from your own CA instead, set:
DUCKDB_QUACK_SSL_CERT_FILE=/certs/quack.pemDUCKDB_QUACK_SSL_KEY_FILE=/certs/quack.keyLocal 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”Prefer to run the same services manually in the DuckDB shell? See Run manually.
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 and makes it the default catalog Quack exposes, so attached clients reach the signal tables (otlp_logs, otlp_traces, …) directly instead of through a fully qualified lake.main.* name.
Once Quack is up, the container prints how to reach it, including the certificate fingerprint:
Query over Quack at https://0.0.0.0:9494TLS certificate SHA-256 fingerprint: 3D:2C:B0:...:4F:8F ATTACH 'quack:127.0.0.1:9494' AS otel (TYPE quack, SSL_FINGERPRINT '3D:2C:B0:...:4F:8F');Run docker logs duckdb-otlp to see it again. 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, attach the remote DuckDB process with the fingerprint the container printed, then connect the session to it:
CREATE SECRET duckdb_otlp_quack ( TYPE quack, SCOPE 'quack:localhost:9494', TOKEN 'dev-quack-token-123456');
ATTACH 'quack:localhost:9494' AS otel (TYPE quack, SSL_FINGERPRINT '<fingerprint>');CONNECT otel;SSL_FINGERPRINT makes the client use TLS and accept only that certificate. Without it, a localhost client tries plain HTTP and fails with Unsupported protocol error. The Docker port mapping forwards local port 9494 to quack:0.0.0.0:9494 inside the container.
The same ATTACH works from a remote host. With a certificate from a CA your clients trust, drop SSL_FINGERPRINT. A tunneling extension such as Query-farm/quackscale keeps the Quack listener off the public network.
Query telemetry with CONNECT
Section titled “Query telemetry with CONNECT”After CONNECT otel, DuckDB routes ordinary SQL to the container’s DuckDB process and streams results back. The daemon makes its telemetry catalog (named lake here) the default, so the signal tables — otlp_logs, otlp_traces, otlp_metrics_gauge, otlp_metrics_sum, otlp_metrics_histogram, otlp_metrics_exp_histogram — are reachable by their plain names.
The server accepts rows before it makes them durable. For a short local test, flush the ingest buffer first:
SELECT * FROM otlp_flush('otlp:0.0.0.0:4318');Then query the signal tables directly:
SELECT time_unix_nano, service_name, severity_text, bodyFROM otlp_logsWHERE service_name = 'quack-local-ducklake-demo'ORDER BY time_unix_nano DESCLIMIT 5;The same works for traces:
SELECT trace_id, name, service_name, duration_time_unix_nanoFROM otlp_tracesORDER BY start_time_unix_nano DESCLIMIT 20;Run server-side SQL
Section titled “Run server-side SQL”While connected, aggregations, DDL, and table functions run in the server session without SQL-in-a-string wrapping:
SELECT service_name, count(*) AS rowsFROM otlp_logsGROUP BY ALLORDER BY rows DESC;Use quack_query for a one-shot call without a connected session, for example in small automation scripts:
FROM quack_query( 'quack:localhost:9494', 'SELECT service_name, count(*) AS rows FROM otlp_logs GROUP BY ALL ORDER BY rows DESC', token := 'dev-quack-token-123456', ssl_fingerprint := '<fingerprint>');When you are done with the interactive remote session, disconnect it:
DISCONNECT;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.
Run manually
Section titled “Run manually”To run this configuration in a DuckDB 2.0+ shell instead of the daemon, execute the SQL below. Replace bracketed values with the corresponding values from this guide. Keep the shell open while clients send telemetry.
mkdir -p data/ducklake/storageduckdb data/duckdb-otlp-control.duckdb-- The daemon embeds otlp statically; the shell loads the extension explicitly.INSTALL otlp FROM community;LOAD otlp;
-- Attach the local DuckLake catalog used by the Docker guide.INSTALL ducklake;LOAD ducklake;ATTACH 'ducklake:data/ducklake/catalog.duckdb' AS lake ( DATA_PATH 'data/ducklake/storage');
-- Create the target schema before starting ingestion.CREATE SCHEMA IF NOT EXISTS lake.main;
-- Match the daemon's default-catalog setting for unqualified queries.USE lake;
-- Start OTLP/HTTP. Seal cadence, file sizes, and buffer limits use defaults;-- override only as needed — see the Live Ingest Reference:-- https://smithclay.github.io/duckdb-otlp/reference/serve/SELECT listen_url, catalog_name, schema_nameFROM otlp_serve( 'otlp:0.0.0.0:4318', catalog := 'lake', schema := 'main', token := 'dev-token-123456', allow_other_hostname := true);
-- The guide's daemon configuration also enables Quack.INSTALL quack;LOAD quack;SELECT listen_uriFROM quack_serve( 'quack:0.0.0.0:9494', token := 'dev-quack-token-123456', allow_other_hostname := true);
-- The fingerprint clients pin: Quack serves TLS on this non-loopback bind.SELECT info['ssl_fingerprint'] FROM quack_server_list();Before closing DuckDB, stop both listeners cleanly:
-- Stop Quack, then commit buffered telemetry and stop OTLP.CALL quack_stop('quack:0.0.0.0:9494');SELECT status, dropped_rowsFROM otlp_stop('otlp:0.0.0.0:4318');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