on GitHub

Host a SQL file

n6kd is a single Go binary. Point it at a .sql file and it becomes a server that DuckDB clients can attach to:

n6kd -f schema.sql

That is the entire application. There is no framework, no ORM, no endpoint to write, and nothing in the path that has to be kept in sync with the schema — the schema is the service.

What it prints

INFO  lockdown on: served connections cannot reach the filesystem
INFO  ran schema.sql (1551 bytes)
INFO  boot SQL finished in 44ms
INFO  startup finished in 55ms
INFO  ready — clients attach with:
INFO    ATTACH 'n6k://localhost:8080' AS demo (TYPE n6k)

The last lines are the point: it tells you the statement your users need. Paste it into any DuckDB with the extension loaded and the tables are there.

The schema file

Ordinary DuckDB SQL. Whatever it creates is what gets served:

CREATE TABLE customers (
    id        INTEGER PRIMARY KEY,
    name      VARCHAR,
    region    VARCHAR,
    signed_up DATE
);

INSERT INTO customers
SELECT i, 'Customer ' || i,
       ['NA', 'EMEA', 'APAC', 'LATAM'][1 + (i % 4)],
       DATE '2024-01-01' + (i % 365)
FROM range(1, 5001) t(i);

It is a DuckDB script, so it can also read Parquet or CSV, attach Postgres, or build derived tables — anything the loader can run at boot, the server can then serve.

Flags

FlagDefaultEffect
-fSQL file run at boot; repeatable, in the order given with -c
-cSQL statements run at boot; repeatable, in the order given with -f
--permsperms.yml naming the catalogs to serve bridged
--addr:8080, or $PORTListen address
--api-key$N6KD_API_KEYRequire this bearer token; without it, anyone who reaches the port is served
--lockdownonServed connections cannot reach the filesystem
--no-lockdownThe inverse, for when client SQL is trusted
--debug$N6KD_DEBUGPer-connection and per-catalog logging
--json-logging$N6KD_JSON_LOGGINGJSON log lines with Cloud Logging severity fields, for log collectors

Two subcommands matter operationally: n6kd install downloads the DuckDB extensions into the extension directory, and n6kd selftest loads them and bridges a throwaway catalog end to end. selftest is worth running as the last step of a container build — a green build then means the extensions actually load on that image, rather than failing on first connect in production.

Lockdown

By default, SQL arriving from a client cannot touch the host filesystem:

CALL demo.exec('SELECT count(*) FROM read_csv(''/etc/passwd'')');
Permission Error: n6k[demo] EXEC: Cannot access file "/etc/passwd"
  - file system operations are disabled by configuration

This matters because you are handing out a SQL interface. DuckDB is a capable engine — read_csv, COPY … TO, INSTALL, and ATTACH all reach outside the database by design. Lockdown removes that reach from served connections while leaving it available to the schema file at boot, which is a different trust level.

A container

The image is the binary plus your data file:

FROM srossross/n6kd:latest
COPY schema.sql /app/schema.sql
docker run -p 8080:8080 -e N6KD_API_KEY=secret your-image

It is stateless and reads $PORT, so any container host takes it unchanged. The demo server behind these pages is this image on Cloud Run.

In-memory means ephemeral

A schema built into memory at boot is rebuilt on every restart, and writes are lost with the process. That is the right shape for a demo or a read-mostly published dataset, and the wrong shape for a system of record. Point the schema at durable storage when you need writes to survive.

What it does not do

n6kd serves whatever the schema file created, to anyone holding the token, with full read and write access. That is fine for a dataset you are publishing and wrong for anything else — which is what the optional second file is for.