OpenDuck is configured through three layers, applied in this order of precedence (highest first):
- CLI flags — explicit flags on
openduckor its subcommands. - Environment variables — picked up automatically by both the server and the client.
- TOML config file — supplied via
--config, fills in unset CLI fields.
DuckDB's own secret system is used for the in-process storage backend (extension side); see Differential storage.
openduck is a single binary with subcommands:
openduck [OPTIONS] # default: gateway + worker in one process
openduck gateway [OPTIONS] # gateway only (use with external workers)
openduck worker [OPTIONS] # worker only (register with a gateway)
openduck query <SQL> [OPTIONS] # send a SQL query to a gateway
openduck cancel <EXEC_ID> [OPTIONS] # cancel a running execution
openduck status [OPTIONS] # health-check a gateway
openduck snapshot seal|list [OPTIONS] # snapshot operations
openduck gc [OPTIONS] # garbage-collect unreferenced layers
Run openduck --help for the full output, or openduck <subcommand> --help.
These flags apply to the default openduck invocation that starts a gateway + worker in a single process.
| Flag | Env | Default | Description |
|---|---|---|---|
-d, --db <NAME|PATH> |
— | (in-memory) | Database name or DuckDB file path |
-p, --token <TOKEN> |
OPENDUCK_TOKEN |
(dev mode) | Access token. Unset = dev mode (any token accepted) |
-l, --listen <ADDR> |
— | 0.0.0.0:7878 |
Gateway listen address |
--worker-listen <ADDR> |
— | 127.0.0.1:9898 |
Internal worker listen address |
--storage <MODE> |
— | direct |
Storage mode: direct, fuse, in-process |
--mountpoint <PATH> |
— | — | FUSE mountpoint (required when --storage fuse) |
--postgres <URL> |
DATABASE_URL |
— | Postgres URL for differential storage metadata |
--data-dir <PATH> |
— | — | Local directory holding sealed layers |
--ducklake-metadata <CONN> |
— | — | DuckLake metadata connection string |
--ducklake-data <PATH> |
— | — | DuckLake data path (e.g. s3://bucket/prefix/) |
--hybrid |
OPENDUCK_HYBRID=1 |
off | Enable hybrid execution (gateway splits LOCAL/REMOTE) |
--max-in-flight <N> |
OPENDUCK_MAX_IN_FLIGHT |
64 |
Max concurrent in-flight executions per gateway |
-v, --verbose |
RUST_LOG |
info |
Repeat for more verbosity (-v = debug, -vv = trace) |
--config <PATH> |
— | — | TOML config file (CLI flags still take precedence) |
| Flag | Env | Default | Description |
|---|---|---|---|
-l, --listen <ADDR> |
— | 0.0.0.0:7878 |
Listen address |
--workers <ADDR,…> |
OPENDUCK_WORKER_ADDRS |
http://127.0.0.1:9898 |
Comma-separated worker endpoints |
| Flag | Env | Default | Description |
|---|---|---|---|
-l, --listen <ADDR> |
— | 127.0.0.1:9898 |
Listen address |
-d, --db <PATH> |
— | (in-memory) | DuckDB file path |
--storage <MODE> |
— | direct |
direct, fuse, or in-process |
--mountpoint <PATH> |
— | — | FUSE mountpoint (with --storage fuse) |
--postgres <URL> |
DATABASE_URL |
— | Postgres URL (with --storage in-process / fuse) |
--data-dir <PATH> |
— | — | Local layer directory |
--ducklake-metadata <CONN> |
— | — | DuckLake metadata |
--ducklake-data <PATH> |
— | — | DuckLake data path |
--gateway <URL> |
— | — | Gateway endpoint to self-register with on startup |
openduck query "SELECT * FROM users LIMIT 10" \
--endpoint http://127.0.0.1:7878 \
--token $OPENDUCK_TOKEN \
--format table # or json | csvopenduck cancel <EXECUTION_ID> --endpoint http://127.0.0.1:7878 --token $OPENDUCK_TOKENopenduck status --endpoint http://127.0.0.1:7878 --token $OPENDUCK_TOKENPrints OK <endpoint> connect=<ms>ms query=<ms>ms on success, exits non-zero otherwise.
# Seal current data into an immutable snapshot, prints the snapshot UUID
openduck snapshot seal --postgres $DATABASE_URL --db mydb --data-dir /var/openduck
# List snapshots for a database
openduck snapshot list --postgres $DATABASE_URL --db mydbopenduck gc --postgres $DATABASE_URL --db mydb --data-dir /var/openduck [--dry-run]Compacts superseded extents, then deletes layers no longer referenced by any snapshot. With --dry-run, only candidates are listed.
| Variable | Default | Description |
|---|---|---|
OPENDUCK_TOKEN |
(dev mode) | Bearer token validated against every RPC. Unset = any token accepted. |
OPENDUCK_HYBRID |
0 |
Set to 1 to enable hybrid plan splitting in the gateway. |
OPENDUCK_MAX_IN_FLIGHT |
64 |
Max concurrent executions per gateway (semaphore-bounded). |
OPENDUCK_WORKER_ADDRS |
http://127.0.0.1:9898 |
Comma-separated worker endpoints (gateway only). |
DATABASE_URL |
— | Postgres URL for diff storage metadata. |
OTEL_EXPORTER_OTLP_ENDPOINT |
— | If set, metrics export to this OTLP endpoint. |
| Variable | Default | Description |
|---|---|---|
OPENDUCK_TOKEN |
— | Token for ATTACH 'openduck:...' when not in the URI. |
OPENDUCK_ENDPOINT |
http://127.0.0.1:7878 |
Gateway endpoint when not in the URI. |
OPENDUCK_EXTENSION_PATH |
auto-detect | Path to the built .duckdb_extension file (used by Python wrapper). |
OPENDUCK_POSTGRES_URL |
— | Fallback Postgres URL for in-process storage when no DuckDB secret is set. |
OPENDUCK_DATA_DIR |
— | Fallback data directory for in-process storage. |
| Variable | Default | Description |
|---|---|---|
OPENDUCK_DUCKLAKE_METADATA |
— | DuckLake metadata connection string. |
OPENDUCK_DUCKLAKE_DATA |
— | DuckLake data path (s3://bucket/prefix/ or local path). |
AWS_ACCESS_KEY_ID |
— | S3 credential (for DuckLake on S3). |
AWS_SECRET_ACCESS_KEY |
— | S3 credential. |
AWS_ENDPOINT_URL |
— | S3 endpoint override (for MinIO). |
A complete reference template is checked in at examples/.env.example.
Pass --config /path/to/openduck.toml to load defaults from a TOML file. CLI flags always take precedence; environment variables fill in fields the file omits.
# openduck.toml — every key is optional
token = "my-secret-token"
listen = "0.0.0.0:7878"
worker_listen = "127.0.0.1:9898"
hybrid = true
max_in_flight = 128
postgres = "postgres://openduck:openduck@db:5432/openduck_meta"
data_dir = "/var/openduck"
ducklake_metadata = "postgres://lake:lake@db:5432/ducklake_meta"
ducklake_data = "s3://my-bucket/lake/"Fields recognised in the TOML file are exactly the ones listed in the top-level flags table above (snake_case form of the long flag).
openduck:<database>?endpoint=<url>&token=<token>[&snapshot=<uuid>][&data_dir=<path>][&secret=<name>]
od:<database>?endpoint=<url>&token=<token>
| Form | Example |
|---|---|
| Canonical | ATTACH 'openduck:mydb?token=xxx' AS cloud; |
| Short alias | ATTACH 'od:mydb?token=xxx' AS cloud; |
| Default DB | ATTACH 'openduck:?token=xxx' AS cloud; |
| Snapshot read | ATTACH 'openduck://mydb/database.duckdb?snapshot=<uuid>' AS s; |
?token=...in the URIOPENDUCK_TOKENenvironment variable
Token validation uses constant-time comparison (subtle::ConstantTimeEq).
?endpoint=...in the URIOPENDUCK_ENDPOINTenvironment variablehttp://127.0.0.1:7878
The extension registers a secret type called openduck_storage:
-- Default secret (no ?secret= needed)
CREATE SECRET openduck_storage (
TYPE openduck_storage,
postgres_url 'postgres://localhost/openduck',
data_dir '/var/openduck'
);
-- Named secret (referenced via ?secret=)
CREATE SECRET prod_storage (
TYPE openduck_storage,
postgres_url 'postgres://prod-host/openduck',
data_dir '/mnt/prod/openduck'
);
ATTACH 'openduck://mydb/database.duckdb?secret=prod_storage' AS prod_db;Resolution order:
?secret=NAMEin the URI- Default secret named
openduck_storage OPENDUCK_POSTGRES_URL+OPENDUCK_DATA_DIRenv vars- In-memory storage (no persistence)
?data_dir=PATH always overrides whichever data_dir was resolved from a secret or env var. postgres_url values are redacted in LIST SECRETS output.
| Limit | Value | Notes |
|---|---|---|
| Max in-flight per gateway | 64 (configurable) |
Backpressure semaphore. |
| Max gRPC message size | 64 MiB | Encode and decode. Larger results stream as multiple Arrow IPC batches. |
| Connect timeout (gateway → worker) | 5 s | |
Per-RPC ExecuteFragment timeout |
300 s | |
Per-RPC CancelExecution timeout |
10 s | |
| Worker heartbeat interval | 30 s | Auto re-register on heartbeat failure. |
- Dev mode —
OPENDUCK_TOKENunset on the server: any token (including empty) is accepted. Logs a warning at startup. - Production —
OPENDUCK_TOKENset: every RPC'saccess_tokenis compared against the server's value with constant-time comparison.
The same logic protects ExecuteFragment, CancelExecution, RegisterWorker, Heartbeat, BeginTransaction, CommitTransaction, RollbackTransaction, and IngestData.
tracing-subscriber reads from RUST_LOG. The -v / -vv flags add directives on top:
# Debug for the gateway, info elsewhere
RUST_LOG=info,exec_gateway=debug openduck -d mydb -p $OPENDUCK_TOKEN
# Trace everything
openduck -vv -d mydb -p $OPENDUCK_TOKENWhen openduck-metrics is built into the binary and OTEL_EXPORTER_OTLP_ENDPOINT is set, the worker exports query latency histograms and seal-duration metrics via OTLP. See Deployment → Observability.
db_nameis checked for path separators and..before being joined into a file path (in-process mode).storage_urivalues from Postgres are validated to reject absolute paths and..components before any file deletion.- The Python client validates
aliasas a strict SQL identifier and single-quote-escapesdb_name,token,endpoint, andextension_path. - DuckLake
metadataanddata_pathstrings are single-quote-escaped before interpolation. postgres_urlis redacted inDebugoutput andLIST SECRETS.
- Getting started for a full walk-through.
- Differential storage for storage modes in depth.
- Hybrid execution for
--hybridandopenduck_run.