From a single Docker container on one node to fleets of DuckDB Quack nodes on your own Kubernetes. Per-tenant isolation, fine-grained ACLs, federated queries. Query it from any ODBC/JDBC/ADBC client. Works with any ETL.
# One endpoint, JDBC / ADBC / ODBC / PyArrow
conn = flight_sql.connect(
"grpc+tls://gateway:31338",
db_kwargs={
"username": "alice", "password": "•••",
tenant: "acme", pool: "analytics"})
# routed to the least-loaded node for the tenant
cur = conn.cursor()
cur.execute("SELECT count(*) FROM orders")
cur.fetchone()
(1500000,)$ uvx qod start
control-plane Postgres: jdbc:postgresql://localhost:5432/qod (user postgres)
Quack on Demand 0.5.0 is up
control plane : jdbc:postgresql://localhost:5432/qod
REST API + UI : http://localhost:20900 (UI: http://localhost:20900/ui)
FlightSQL : grpc+tls://localhost:31338
Client connection strings (replace <tenant>, <pool>, <user>):
JDBC : jdbc:arrow-flight-sql://localhost:31338/?tenant=<tenant>&pool=<pool>&user=<user>&useEncryption=true&disableCertificateVerification=true
ADBC : uri=grpc+tls://localhost:31338 (adbc_driver_flightsql)
ODBC : Driver={Arrow Flight SQL ODBC Driver};Host=localhost;Port=31338;...
One command boots the whole platform: manager, FlightSQL endpoint, admin UI, and demo data. No cluster, no YAML, no prerequisites beyond Python and a JVM.
The boot banner hands you working JDBC, ADBC, and ODBC connection strings. Paste one into your tool and you are querying.
qod> SELECT o_orderpriority, count(*) FROM orders GROUP BY 1 ORDER BY 1; +-----------------+----------+ | o_orderpriority | count(*) | +-----------------+----------+ | 1-URGENT | 30044 | | 2-HIGH | 30076 | | 3-MEDIUM | 29949 | +-----------------+----------+ 3 rows in 0.04s
One Arrow FlightSQL endpoint serves every client: JDBC and ODBC for BI tools, ADBC and Python for notebooks, the qod CLI for your terminal.
Statements are classified read or write and routed to the least-loaded node that can serve them, with transaction pinning, behind that single endpoint.
Tenants own databases, users, and pools of DuckDB nodes, isolated by storage layout and access control.
Idle pools suspend to zero nodes and keep their catalogs; the first query wakes them automatically. You pay for compute only while queries run.
| c_phone | c_mktsegment |
|---|---|
| *** | BUILDING |
| *** | BUILDING |
| *** | BUILDING |
| c_phone | c_mktsegment |
|---|---|
| 25-989-741-2988 | BUILDING |
| 23-768-687-3665 | AUTOMOBILE |
| 13-761-547-5974 | MACHINERY |
The same query, two users, two answers: that is the access-control model working.
Tenant isolation at the edge, per-table grants (read, write, DDL), row-level security filters, and column masking are all enforced before a statement ever reaches a node.
DuckLake gives every table a history: time-travel reads, named tags, undrop for deleted tables, and restore that rolls back as a new snapshot with nothing lost.
Mistakes stop being incidents and become queries.
Per-statement history with latency and routing, a full audit trail, usage trends per tenant, and Prometheus metrics for your dashboards.
The single-container mode is first-class, not a demo: run it on one box for years if that is all you need.
When you outgrow it, point the same gateway at your Postgres and object store, then move to Kubernetes with pools of pods and HA managers. Your clients never notice: same endpoint, same tenants, same SQL.
Many users on shared DuckDB/DuckLake data, access control on every query, horizontal scale, tenant isolation, or federation. Querying one local file? Use DuckDB directly.