Skip to content

Repository files navigation

simple-quack-server

A thin, long-lived runtime for DuckDB's Quack protocol, packaged as a container image and a Helm chart.

DuckDB's quack extension turns any DuckDB session into a server (CALL quack_serve(...)), but ships no daemon: serving lasts only while the calling process holds its connection open. This project is that process — nothing more. It attaches a database (DuckLake first-class), starts quack_serve, and stays alive until SIGTERM. Authentication, authorization and the protocol itself belong to duckdb-quack and its ecosystem (e.g. quack-oauth); keeping this server simple is a design goal, not a limitation.

Serving a DuckLake this way means clients need only a URL and a token — PostgreSQL (catalog) and object storage (data files) stay private:

LOAD quack;
ATTACH 'quack:lake.example.com:443' AS lake (TOKEN '<token>');
FROM lake.query('USE lake');                     -- once per session, see below
SELECT * FROM lake.my_table;                     -- reads, INSERT and CTAS work directly
SELECT * FROM lake.query('MERGE INTO ...');      -- UPDATE/DELETE/ALTER/MERGE/time travel

How it works

  • The server attaches your DuckLake (or runs an arbitrary INIT_SQL_FILE) and calls quack_serve with allow_other_hostname = true. TLS termination is expected at the ingress/proxy layer, as recommended by the quack documentation.
  • Clients must switch to the attached database once per session (FROM <alias>.query('USE lake')). The quack client sends SQL without the catalog qualifier, so unqualified names resolve in the server session's default database — which starts out as the empty in-memory one. USE moves it; the server cannot do this on the client's behalf (search_path cannot be set globally, and quack creates each session's connection without init SQL). Tables in schemas other than main need USE lake.<schema>, or SET search_path = 'lake.main,lake.<schema>' for several at once.
  • After that, SELECT, INSERT and CREATE TABLE ... AS work directly against <alias>.<table>. UPDATE, DELETE, ALTER TABLE, MERGE INTO, time travel (AT (VERSION => n)) and DuckLake table functions are not implemented in the quack client yet (independently of which database is used) — wrap those in <alias>.query('...').
  • The client loads the catalog when ATTACH runs, so tables created afterwards become visible on re-attach. If the server restarts, the session dies with Invalid connection id and the client has to re-attach.
  • One replica = one writer. DuckLake's optimistic concurrency handles concurrent commits from other writers, but this server is intentionally a single process.

Configuration

All configuration is via environment variables. In Kubernetes they are injected from an existing Secret (envFrom); see the Helm section below.

Variable Required Default Description
QUACK_TOKEN yes Client authentication token
QUACK_PORT 9494 Listen port
CATALOG_HOST yes* DuckLake catalog (PostgreSQL) host
CATALOG_PORT 5432 Catalog port
CATALOG_DB yes* Catalog database name
CATALOG_USER yes* Catalog user
CATALOG_PASSWORD yes* Catalog password
S3_ENDPOINT yes* Object storage endpoint
S3_ACCESS_KEY yes* Object storage access key
S3_SECRET_KEY yes* Object storage secret key
DATA_PATH yes* Data path, e.g. s3://ducklake/main
S3_USE_SSL false Use TLS for object storage
S3_REGION us-east-1 Object storage region
ATTACH_ALIAS lake Alias of the attached database
INIT_SQL_FILE SQL file to run instead of CATALOG_*

* required unless INIT_SQL_FILE is set.

Helm

The chart references an existing Secret for the token and connection settings (auth.existingSecret, injected via envFrom). Create it with your own tooling — plain kubectl create secret, External Secrets Operator, etc.

kubectl create secret generic quack-server -n quack \
    --from-literal=QUACK_TOKEN=... \
    --from-literal=CATALOG_HOST=postgres-cluster-rw.postgres \
    --from-literal=CATALOG_PORT=5432 \
    --from-literal=CATALOG_DB=ducklake_catalog \
    --from-literal=CATALOG_USER=ducklake \
    --from-literal=CATALOG_PASSWORD=... \
    --from-literal=S3_ENDPOINT=http://rustfs.rustfs:9000 \
    --from-literal=S3_ACCESS_KEY=... \
    --from-literal=S3_SECRET_KEY=... \
    --from-literal=DATA_PATH=s3://ducklake/main

helm install quack charts/simple-quack-server -n quack \
    --set auth.existingSecret=quack-server

Expose it with the standard ingress values (ingress.enabled, ingress.hosts). Remote clients must specify the ingress port explicitly (quack:host:443) because the quack default port 9494 is usually not proxied.

Development

Tools are managed with mise: mise install.

just serve          # run locally (configuration via environment variables)
just test           # lifecycle test: local DuckLake, client roundtrip, SIGTERM
just image-build    # build the image
just helm-lint      # lint the chart
just helm-template  # render the chart
just client-test localhost:9494 <token>   # smoke test a running server
just helm-test <namespace>                # run the chart's test hook (read-only)

just test needs nothing but uv and the ducklake extension: it starts the server on a temporary local DuckLake, attaches as a client, checks the USE contract with a read and an insert, then requires SIGTERM to stop the server cleanly. just helm-test runs helm test, which attaches to a deployed release from inside the cluster and reads the catalog — no writes to the real lake. Set tests.enabled=false to leave the hook out of the release.

Notes

  • Clients assume HTTPS for non-localhost hosts. Inside a cluster (plain HTTP), attach with (DISABLE_SSL true); behind a TLS-terminating ingress, omit it.
  • Earlier versions mirrored the attached tables as views in the default database so that remote.<table> resolved without USE. That has been removed: the mirror views shadowed the real tables in the client's catalog (making them read-only even after USE), covered the main schema only, and lagged behind DDL. A client that cannot issue a single statement at session start cannot ATTACH either, so nothing is lost.
  • Authentication is a single shared token for now. For OAuth 2.1 / OIDC (per-user tokens, claim-based authorization, audit), see quack-oauth — planned as an optional integration.

License

MIT

About

A thin, long-lived runtime for DuckDB's Quack protocol, packaged as a container image and a Helm chart.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages