Multi-tenant Arrow Flight SQL gateway for DuckDB: Pluggable auth, , RBAC, federated queries
10K+
Multi-tenant Arrow Flight SQL gateway for DuckDB. Per-tenant catalog, federated queries (iceberg, s3 ...), per-tenant node pools, a first-class RBAC graph (users · groups · roles · table permissions · pool grants), and pluggable identity (DB / JWT / OIDC). Single uber-jar, shipped as one image.
DuckDB's Quack protocol lets DuckDB instances talk over HTTP/2, but it is intentionally minimal: one static token, no multi-tenancy, no authorization, DuckDB-only clients. Quack on Demand is the infrastructure you put in front of it.
Any JDBC / ODBC / ADBC / PyArrow client connects to a single Flight SQL edge. The gateway authenticates the user, gates the connection at handshake (user-scope + pool-access), parses each statement and matches its table references against the user's cached EffectiveSet, then routes the statement to a compatible node in the tenant's pool.
Status: Designed to be safely restartable; single-instance (no active-active manager yet). Config keys and APIs may change between
0.xreleases.
latest — most recent published build0.2.x), set QOD_VERSION in Compose to pinImages are multi-arch: linux/amd64 and linux/arm64. Runtime base is eclipse-temurin:17-jre.
A reachable PostgreSQL instance. It holds the control plane (qodstate_* tables in the qod database) and one DuckLake catalog database per tenant (${tenant}_${tenantDb}). The Compose file below bundles one.
services:
postgres:
image: postgres:16-alpine
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: change-me
POSTGRES_DB: qod
volumes:
- ./pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres -d qod"]
interval: 5s
timeout: 3s
retries: 20
quack:
image: starlakeai/quack-on-demand:latest
depends_on:
postgres:
condition: service_healthy
environment:
QOD_PG_HOST: postgres
QOD_PG_PORT: "5432"
QOD_PG_USER: postgres
QOD_PG_PASSWORD: change-me
QOD_PG_DBNAME: qod
QOD_ADMIN_USERNAME: admin
QOD_ADMIN_PASSWORD: change-me
PROXY_TLS_ENABLED: "false"
ports:
- "20900:20900" # REST + admin UI
- "31338:31338" # Flight SQL edge
- "21900-22500:21900-22500" # per-tenant Quack node port range
volumes:
- ./ducklake:/app/ducklake
- ./certs:/app/certs
docker compose up
Admin UI: http://localhost:20900/ui/ (log in with the admin credentials above). The full repo ships a richer Compose stack with a seeded TPC-H tenant, S3/SeaweedFS, and a Prometheus + Grafana profile — see the project README and guides/QUICKSTART.md.
docker run)Point it at an already-running PostgreSQL:
docker run --rm \
-p 20900:20900 -p 31338:31338 -p 21900-22500:21900-22500 \
-e QOD_PG_HOST=host.docker.internal \
-e QOD_PG_PORT=5432 \
-e QOD_PG_USER=postgres \
-e QOD_PG_PASSWORD=change-me \
-e QOD_PG_DBNAME=qod \
-e QOD_ADMIN_USERNAME=admin \
-e QOD_ADMIN_PASSWORD=change-me \
-e PROXY_TLS_ENABLED=false \
-v "$PWD/ducklake:/app/ducklake" \
-v "$PWD/certs:/app/certs" \
starlakeai/quack-on-demand:latest
| Port | Purpose |
|---|---|
20900 | REST API + React admin console (/ui/, /metrics) |
31338 | Arrow Flight SQL edge |
21900-22500 | Default lease range for local-mode Quack nodes |
Every config key is overridable via a QOD_* environment variable.
| Variable | Default | Description |
|---|---|---|
QOD_PG_HOST / QOD_PG_PORT | — / 5432 | PostgreSQL host / port |
QOD_PG_USER / QOD_PG_PASSWORD | — | PostgreSQL credentials |
QOD_PG_DBNAME | qod | Control-plane database |
QOD_PG_SCHEMA | main | Control-plane schema |
QOD_ADMIN_USERNAME | admin | Superuser login(s), comma-separated |
QOD_ADMIN_PASSWORD | admin | Superuser password — change before exposing |
QOD_API_KEY | — | Static X-API-Key for the admin REST API |
PROXY_TLS_ENABLED | false | Enable TLS on the Flight SQL edge (self-signed cert auto-generated) |
QOD_DUCKLAKE_DATA_PATH | /app/ducklake/tpch | DuckLake data root; set to s3://…, gs://…, az://… for object storage |
QOD_MIN_PORT / QOD_MAX_PORT | 21900 / 22500 | Quack node port lease range |
QOD_S3_ENDPOINT / QOD_S3_ACCESS_KEY_ID / QOD_S3_SECRET_ACCESS_KEY / QOD_S3_REGION | — | S3-compatible object storage for DuckLake parquet |
Persist ./ducklake (data files) and ./certs (TLS cert/key, generated on first boot) with volumes.
Each session scopes itself with tenant + pool routing parameters.
JDBC
jdbc:arrow-flight-sql://localhost:31338?useEncryption=true&disableCertificateVerification=true&user=admin&password=admin&tenant=<tenant>&pool=<pool>
ADBC (Python)
import adbc_driver_flightsql.dbapi as flight_sql
conn = flight_sql.connect(
"grpc+tls://localhost:31338",
db_kwargs={
"username": "admin",
"password": "admin",
"adbc.flight.sql.rpc.call_header.tenant": "<tenant>",
"adbc.flight.sql.rpc.call_header.pool": "<pool>",
"adbc.flight.sql.client_option.tls_skip_verify": "true",
},
)
Works with any Flight-aware client: DBeaver, PyArrow, Spark, ODBC via the Apache Arrow Flight SQL driver.
READONLY / WRITEONLY / DUAL nodes; the router classifies each statement and picks a compatible node/metrics, or push to CloudWatch / Azure Monitor / GCP; ships a Grafana dashboardguides/ in the repoai.starlake:quack-on-demand_3Apache License 2.0.
Content type
Image
Digest
sha256:e57379806…
Size
441.4 MB
Last updated
1 day ago
docker pull starlakeai/quack-on-demand