Search Shortcut cmd + k | ctrl + k
harbor

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 []