Client/server DuckDB over one HTTP port — web UI, JSON /sql, and native ATTACH (TYPE harbor)
Maintainer(s):
shreeve
Installing and Loading
INSTALL harbor FROM community;
LOAD harbor;
Example
-- Load the extension and start the server in the background.
-- harbor_serve returns once the server is accepting connections;
-- harbor_wait keeps the process alive in daemon mode (containers,
-- systemd).
LOAD harbor;
CALL harbor_serve(bind := '127.0.0.1', port := 9494);
-- ┌─────────────────────────────────┐
-- │ token │
-- ├─────────────────────────────────┤
-- │ a1b2c3d4e5f6... │
-- └─────────────────────────────────┘
-- Hit it from another shell:
-- curl http://127.0.0.1:9494/health
-- curl -H "Authorization: Bearer <token>" \
-- -X POST http://127.0.0.1:9494/sql \
-- -H 'Content-Type: application/json' \
-- -d '{"sql":"SELECT 42 AS the_answer"}'
--
-- {"ok":true,"kind":"select","columns":[{"name":"the_answer","duckdbType":"INTEGER","lossless":true}],
-- "data":[[42]],"rowCount":1,"timeMs":0}
-- Browser users open http://127.0.0.1:9494/ — the harbor login
-- page asks for the token once, sets a HttpOnly SameSite=Strict
-- cookie, then proxies the official DuckDB UI through.
-- A second DuckDB attaches the server's catalog natively — catalog
-- discovery and table scans both ride /sql, so multi-table JOINs,
-- projection pushdown, and CLI autocomplete all work:
-- LOAD harbor;
-- ATTACH 'harbor:127.0.0.1:9494' AS h (TYPE harbor, TOKEN '<token>');
-- SELECT * FROM h.main.orders o JOIN h.main.customers c USING (customer_id);
-- Stop the server:
CALL harbor_stop();
About harbor
harbor is a single DuckDB extension. When loaded, your DuckDB
instance turns into a client/server database on one HTTP port:
| Surface | Endpoints | What it gives you |
|---|---|---|
| JSON SQL | POST /sql, /sql/sessions/* |
NDJSON streaming + one-shot JSON, prepared params (incl. LIST/STRUCT/MAP), principal-owned sessions, harbor_query_timeout_s enforcement |
| Native attach | client-side ATTACH 'harbor:host' (TYPE harbor) |
Browse and query the server's catalog from any DuckDB — multi-table JOINs, projection pushdown, and CLI autocomplete; catalog + scans both ride /sql (read-only) |
| DuckDB UI | GET /, /ddb/*, /localEvents |
Cookie-gated proxy to the official DuckDB UI; CSP+nonce on the login page |
| Admin | /health, /whoami, /tables, /schema/:db/:t, /checkpoint, /sessions, /interrupt |
Centralized __HARBOR_ADMIN__:resource:action default-deny + harbor_allow_admin_without_authz operator opt-in |
| Auth | /auth/login, /auth/logout |
Bearer / X-Harbor-Token / HMAC-signed harbor_session cookie |
All on the same port, against the same in-process DuckDB, with the same session pool and auth model.
Use cases:
- Operate one DuckDB process and let users hit it via whatever
interface fits their tool: browser (UI), DuckDB CLI/clients
(
ATTACH ... TYPE harbor), backend code (/sql). - Bridge between SQL clients and apps without standing up a separate database server.
- Container-native deployments —
harbor_wait()blocks until SIGTERM so harbor runs cleanly under systemd / Docker / Kubernetes.
Status: v0.3 release, tested across the DuckDB community-extension matrix (linux_amd64/arm64, osx_amd64/arm64, windows_amd64/_mingw, wasm_mvp/eh/threads).
Source, design spec, contributor guide, full release notes: github.com/shreeve/duckdb-harbor.
Vendors the official duckdb-ui; targets DuckDB v1.5.3.
Added Functions
| function_name | function_type | description | comment | examples |
|---|---|---|---|---|
| harbor_check_token | scalar | NULL | NULL | |
| harbor_identify | table | NULL | NULL | |
| harbor_nop_authorization | scalar | NULL | NULL | |
| harbor_serve | table | NULL | NULL | |
| harbor_stop | table | NULL | NULL | |
| harbor_version | scalar | NULL | NULL | |
| harbor_wait | table | NULL | NULL | |
| whoami | table_macro | NULL | NULL |
Overloaded Functions
This extension does not add any function overloads.
Added Types
This extension does not add any types.
Added Settings
| name | description | input_type | scope | aliases |
|---|---|---|---|---|
| harbor_allow_admin_without_authz | When true AND no custom harbor_authorization_function is set, admin endpoints (HARBOR_ADMIN:*) bypass the internal default-deny rule. Off by default; harbor_serve logs a loud WARN at startup when the combination is in effect. | BOOLEAN | GLOBAL | [] |
| harbor_auth_cookie_ttl_s | TTL in seconds for HMAC-signed harbor_session cookies issued by /auth/login (default 12h) | UBIGINT | GLOBAL | [] |
| harbor_authentication_function | Name of a Harbor callback function for authentication | VARCHAR | GLOBAL | [] |
| harbor_authorization_function | Name of a Harbor callback function for authorization | VARCHAR | GLOBAL | [] |
| harbor_cors_origins | Comma-separated allow-list of origins for cross-origin /auth/, /sql, /info (empty = no cross-origin permitted; '' is rejected) | VARCHAR | GLOBAL | [] |
| harbor_loaded_at_us | Epoch microseconds at extension load | BIGINT | GLOBAL | [] |
| harbor_max_request_body_bytes | Maximum POST body size for /sql JSON requests; larger requests return 413 PAYLOAD_TOO_LARGE (default 256 MiB) | UBIGINT | GLOBAL | [] |
| harbor_max_response_rows | Cap on rows returned per /sql request; 0 = unlimited; truncation reflected in the NDJSON end record's truncated:true field (default 0) | UBIGINT | GLOBAL | [] |
| harbor_max_sessions | Maximum concurrent DB sessions across all principals; new session creation past this limit returns 429 SESSION_LIMIT (default 1024) | UBIGINT | GLOBAL | [] |
| harbor_query_timeout_s | Per-query wall-clock timeout in seconds. 0 disables the timeout. Non-zero values interrupt any Connection::Execute (/sql, /ddb/run, admin transients) that runs longer than the configured limit, returning HTTP 504 with errorCode QUERY_TIMEOUT. Default 0. | UBIGINT | GLOBAL | [] |
| harbor_stop_drain_timeout_s | Seconds harbor_stop waits for in-flight request handlers to drain per attempt before re-checking active requests (default 30; 0 = poll without waiting) | UBIGINT | GLOBAL | [] |
| ui_polling_interval | UI catalog watcher polling interval in milliseconds (0 disables) | UINTEGER | GLOBAL | [] |
| ui_remote_url | Remote URL the UI proxies GET /.* requests to (default ui.duckdb.org) | VARCHAR | GLOBAL | [] |
| whoami_hostname | Network hostname / public address | VARCHAR | GLOBAL | [] |
| whoami_meta | Provider-specific metadata as JSON | VARCHAR | GLOBAL | [] |
| whoami_name | Human-readable name for this node | VARCHAR | GLOBAL | [] |
| whoami_provider | Deployment provider (ec2, docker, local, …) | VARCHAR | GLOBAL | [] |
| whoami_region | Deployment region | VARCHAR | GLOBAL | [] |
| whoami_started_at | Node start time (ISO-8601 TIMESTAMP) | VARCHAR | GLOBAL | [] |