Skip to content

Command line

pondra is one binary. On its own it opens a SQL shell on a lake. pondra serve runs a node on a lake, or serves a folder of lakes as databases, pondra sql and pondra run run SQL, pondra workspace keeps the lake’s files in a folder of yours, pondra plan and pondra deploy make a database match a project of files, and pondra catalog prints what the lake’s catalog holds.

Command Does
pondra [LAKE] a SQL shell on a lake (./lake if you name none)
pondra serve [PATH] run a node on a lake, or serve every lake in a folder as a database
pondra sql --dir LAKE "SQL" run one statement against the lake, with no server
pondra run FILE LAKE run a SQL file, its $name parameters taking the values you pass
pondra workspace pull|push DIR LAKE the lake’s files in a folder of yours, for git and your editor
pondra init, plan, deploy, test, export a project of files, made true in a database (environments)
pondra login URL, branch, diff a server’s token kept; a database branched from another; what a branch changes
pondra catalog --dir LAKE PREFIX print catalog entries
pondra --version, pondra --help the version, and help for any command

A lake is a folder or an object-store prefix (s3://bucket/prefix). Credentials and the endpoint come from the AWS_* environment variables: see environment.

pondra opens a shell on ./lake, and pondra some/folder or pondra s3://bucket/prefix opens another. The shell runs a node on the lake, in the background, so views, tasks and windows keep running while it is open and other nodes can join. When it ends, the lake is left as any node leaves it. Because this is your own node, its SQL may read files on your machine (SELECT * FROM 'jan.csv'). Other lakes in the current folder are attached as databases.

Statements end with ;. A line can be one of:

Line Does
.tables the tables and views of every lake, with their kind
.databases this lake and the ones attached to it
.quit, .exit, \q leave

Answers print as a table. On a terminal the shell also shows how long each statement took. The shell reads standard input, so it takes a script too:

Terminal window
printf 'CREATE TABLE orders (id BIGINT, amount DOUBLE);\nINSERT INTO orders VALUES (1, 7.5), (2, 4.5);\nSELECT sum(amount) AS total FROM orders;\n' | pondra shell-lake
{"table":"orders","publish":[]}
{"rows":2}
+-------+
| total |
+-------+
| 12.0 |
+-------+

A statement that fails prints Error: … on standard error and the shell goes on.

pondra serve [PATH] runs a node and stays up until Ctrl-C or SIGTERM. PATH is a lake (a local folder, or s3://bucket/prefix), or a folder whose subfolders hold lakes. It is the current folder if you give none. Nodes started on the same lake form a cluster: the first leads (ingest, tiering, commits), and the rest follow (SQL, task shards, writes forwarded to the leader) and take over if the leader dies. A node that is stopped hands over at once, and every write it acknowledged is already durable.

Terminal window
pondra serve lake
pondra serve lake --addr 127.0.0.1:8081
pondra serve data

The second line starts a second node on the same lake. It follows the first. The third serves the lakes in data as databases: see A folder of lakes.

pondra serve looks at PATH and does the first of these that fits:

PATH pondra serve
holds a lake (it has a catalog/ folder) serves that lake
has subfolders that hold lakes serves each as a database named after its folder
is new or empty makes a new lake there, and serves it

Two guards keep a mistyped path from making a lake where you did not mean to:

  • pondra serve with no PATH never makes anything. If the current folder is not a lake and holds no lakes, it stops and says so: pondra serve lake makes one at ./lake.
  • A folder that holds other things (files, other folders) but no lakes is refused. Serve a lake in it (pondra serve folder/lake), or make one there anyway with --lake folder.

For scripts and services (systemd, Docker), name what you mean, and nothing is guessed:

Terminal window
pondra serve --lake /srv/lake
pondra serve --lakes /srv/data

--lake serves exactly this lake, and makes it if it is missing (--dir is the same, for older scripts). --lakes serves exactly this folder of lakes. Use these two in anything that starts a node unattended.

Flag Default Meaning
PATH the current folder the lake (a folder, or s3://bucket/prefix, its credentials and endpoint from the AWS_* variables), or a folder of lakes
--lake LAKE none exactly this lake, made if it is missing, and nothing guessed. For scripts and services (--dir is the same)
--lakes FOLDER none exactly this folder of lakes, each a database. For scripts and services. --lake on a folder of lakes, or --lakes on a lake, is refused and says which you meant
--default NAME lake, else the only one serving a folder of lakes: the database a client that names none gets (HTTP without /db/, Postgres without a database)
--addr ADDR 127.0.0.1:8080 the address the HTTP API and the console listen on, and other nodes reach this one at
--advertise ADDR --addr the address other nodes reach this one at, when it isn’t --addr. A database’s node, behind the process that serves its folder, is host:port/db/name
--reader off a read-only node: SQL only, never leads. Any number can run beside the writer
--python PYTHON none run Python functions and procedures (LANGUAGE python) with this Python, which has the pondra package and pyarrow. auto takes the first one found. Routines run on warm workers beside the node, gone after a minute idle
--stop-with-stdin off stop, as on Ctrl-C, when standard input closes: when the program that started the node ends, however it ends
Flag Default Meaning
--pg ADDR off also speak the Postgres protocol here, such as 0.0.0.0:5432: Postgres port
--kafka ADDR off also speak the Kafka protocol here, such as 0.0.0.0:9092: Kafka protocol
--kafka-advertise HOST:PORT the host of --addr, the port of --kafka where Kafka clients are told to connect to this node
--flight ADDR off also speak Arrow Flight and Flight SQL here, such as 0.0.0.0:8815: Arrow Flight

The HTTP API, /mcp and the Iceberg REST catalog are on --addr.

Flag Environment Meaning
--read-token TOKEN PONDRA_READ_TOKEN reading needs any token
--write-token TOKEN PONDRA_WRITE_TOKEN writing rows needs this one or the admin token
--admin-token TOKEN PONDRA_ADMIN_TOKEN creating tables, views, tasks and functions needs this one

Give every node the same tokens. With none set there are no checks. A flag beats its environment variable. See HTTP for what each role may do. Prefer the environment: a flag shows in the process list.

Flag Environment Meaning
--tls-cert FILE PONDRA_TLS_CERT a PEM certificate: HTTP, Postgres, Kafka and Flight take TLS on their own ports, and plain connections only from this machine
--tls-key FILE PONDRA_TLS_KEY its key
--tls-ca FILE PONDRA_TLS_CA the authority that signs the nodes’ certificates: mutual TLS between nodes

See security. Serving a folder of lakes, the server takes TLS, and each database’s node, on this machine only, doesn’t.

Flag Default Meaning
--ack durable|replicated durable when a write is acknowledged. durable: once it is in the bucket (a millisecond on local disk, hundreds on S3 or R2). replicated: once --replicas nodes hold it, which takes milliseconds on any storage. Give every node the same setting
--replicas N 2 with --ack replicated: how many nodes hold a write before it is acknowledged (the leader is one). Until enough followers are up, writes are acknowledged once durable
--fsync off with --ack replicated: followers flush each copy to disk before acknowledging it, so an acknowledged write survives a power loss of a follower too. It costs a disk flush per change
--flush-ms N 0 the least number of milliseconds between a node’s flushes. 0 flushes as soon as the last flush is committed, and what queued up meanwhile goes together: the lowest latency
--backlog N 10000000 rows allowed to wait in the log for tiering before commits pause. Lower it for less memory, raise it to absorb longer bursts
Flag Default Meaning
--tier-secs SECS 10 tiering starts as soon as rows commit, at most once per this many seconds (fractions are fine). It decides how soon new rows are Parquet, and a Delta or Iceberg version other engines can read. Pondra’s own reads see every commit at once. 0 tiers only on POST /tier. A table with a million rows waiting is tiered within a second anyway
--task-ms N 1000 streaming tasks run as soon as new rows commit, and at least this often (in milliseconds)
--retain-secs N 60 how long to keep consumed log segments and replaced files before deleting them
--changelog-secs N 0 keep the log this many seconds as a change feed: /watch/{table}?after=N and Kafka consumers can replay every change since then. 0: only --retain-secs
--publish FORMATS none open formats new tables are also published in, for engines that do not know Pondra: delta, iceberg or delta,iceberg. A table can set its own with publish
Flag Default Meaning
--memory-gb GB a third of the machine’s memory for queries (also PONDRA_MEMORY_GB). A sort, join or aggregation that needs more spills to the temp directory
--cache-dir DIR <temp dir>/pondra-cache for a lake on object storage: where the node keeps its local SSD copy of recent objects
--cache-gb GB 20, or a quarter of the free disk if that is less the size of that SSD tier. 0 turns it off
Flag Meaning
--attach NAME=DIR read another lake as name.table, and write to it through its own leader. Repeat it for more. Several clusters, each leading its own lake, can share one bucket this way
--attach-found FOLDER for as long as the node runs, attach the lakes in this folder (its subfolders that hold one) under their folder names. Nothing is saved in the catalog. The shell passes its own folder
Terminal window
pondra serve s3://my-bucket/lake --addr 10.0.0.5:8080 \
--pg 0.0.0.0:5432 --kafka 0.0.0.0:9092 --kafka-advertise 10.0.0.5:9092 --flight 0.0.0.0:8815 \
--ack replicated --attach sales=s3://my-bucket/sales

pondra serve data serves every lake in the folder as a database, behind one HTTP port and one Postgres port. Postgres clients pick a database by name, HTTP clients add /db/{name} to the URL, and the console at / lists them all. Each database runs as a node of its own, started when first used and stopped when idle (PONDRA_DATABASE_IDLE_SECS, 600). See Many databases.

Terminal window
pondra serve data --addr 0.0.0.0:8080 --pg 0.0.0.0:5432

Every other option of pondra serve applies to each database’s node: --pg, the tokens (--read-token, --write-token, --admin-token), the TLS files (to the server’s own doors), --python, --reader (every database read-only), --tier-secs, --memory-gb, --cache-dir, --cache-gb, --ack, --replicas, --fsync, --publish, --flush-ms, --task-ms, --retain-secs, --changelog-secs and --backlog. Each database is a process of its own, so a value such as --memory-gb is for each database’s node, not for all of them together.

--flight, --kafka (and --kafka-advertise), --attach, --advertise and --attach-found are refused, and the message names the flag. A Flight or Kafka port speaks for one lake today. Routing them to many databases (Kafka by topic, such as sales.orders, and Flight by the database the client names) is planned for the next round. Serve such a lake on its own:

Terminal window
pondra serve data/sales --flight 0.0.0.0:8815

pondra sql --dir LAKE "SQL" runs one statement against the lake, with no server. A query reads the lake (and files on this machine: SELECT * FROM 'x.parquet') and prints its rows as a table. A write (CREATE, INSERT, UPDATE, DELETE) runs here too: the leader records it over HTTP, or through the bucket if this machine cannot reach the leader, or this process does if nobody leads. A write prints its outcome as JSON.

Option Default Meaning
--dir DIR (or --lake) required the lake
--attach NAME=DIR none another lake to read as name.table. Repeat it for more
Terminal window
pondra sql --dir shell-lake "SELECT id, amount * 2 AS double_amount FROM orders ORDER BY id"
pondra sql --dir shell-lake "INSERT INTO orders VALUES (3, 1.0)"
+----+---------------+
| id | double_amount |
+----+---------------+
| 1 | 15.0 |
| 2 | 9.0 |
+----+---------------+
{"committed":true}

pondra run FILE LAKE runs a SQL file: its statements in order, and $name in them takes the value of --name on the command line. With a lake, it starts a node for the file, and stops it when the file is done. With --url it runs the file on a node that is already running. Numbers and true or false are passed as such, and anything else as text. The last statement’s rows print as a table.

Option Default Meaning
FILE required the SQL file
LAKE lake the lake, unless --url
--url URL none run on the node at this URL
--token TOKEN PONDRA_TOKEN a token for that node
--NAME VALUE none the file’s parameters, after LAKE
--help this, and with FILE the file’s parameters: each DECLARE PARAMETER’s type, default and comment
Terminal window
printf 'SELECT count(*) AS orders, $day AS day FROM orders;\n' > report.sql
pondra run report.sql shell-lake --day 2026-09-27
+--------+------------+
| orders | day |
+--------+------------+
| 3 | 2026-09-27 |
+--------+------------+

A file that declares its parameters (DECLARE PARAMETER $day DATE = current_date - 1;, with what each means in the comment above it: see Variables) says so with --help, and a value given replaces the default, cast to its type:

Terminal window
printf -- '-- The day to count\nDECLARE PARAMETER $day DATE = current_date - 1;\nSELECT count(*) AS orders, $day AS day FROM orders;\n' > daily.sql
pondra run daily.sql --help
pondra run daily.sql shell-lake --day 2026-09-27
Usage: pondra run daily.sql [LAKE] [--day DATE]
Parameters of daily.sql:
--day DATE The day to count · default: current_date - 1
Options: --url URL (a node already running), --token TOKEN (also PONDRA_TOKEN)
+--------+------------+
| orders | day |
+--------+------------+
| 3 | 2026-09-27 |
+--------+------------+

pondra workspace pull DIR LAKE writes the lake’s files into DIR; pondra workspace push DIR LAKE sends back what changed there since. A file changed on both sides is listed and left as it is, and the command exits 1. See In git.

Option Default Meaning
DIR required the folder (a pull makes it)
LAKE lake the lake, unless --url
--url URL none sync with the node at this URL
--token TOKEN PONDRA_TOKEN a token for that node: a writer’s for a push
Terminal window
mkdir -p ws/etl && printf 'SELECT count(*) AS orders FROM orders;\n' > ws/etl/count.sql
pondra workspace push ws shell-lake
pondra workspace pull ws shell-lake
pushed 1, deleted 0 in the lake
pulled 0, removed 0 here

pondra plan, deploy and the project’s commands

Section titled “pondra plan, deploy and the project’s commands”

A project is a folder with pondra.toml, objects/, migrations/ and tests/ (deploy a project). Each command reads it from --project (this folder by default) and talks to an environment’s node.

Command Does
pondra init [DIR] start a project: pondra.toml, objects/, migrations/, tests/ (--name)
pondra plan what a deploy would change, and what it refuses (exit 1 if anything is refused)
pondra deploy [--test] [--prune] the plan, then each step; --test runs tests/ after it, --prune drops what the project no longer declares (exit 1 if it fails)
pondra test run tests/: any row a query returns is a failure (exit 1 if one fails)
pondra export [DIR] the environment’s objects as a project’s files (pondra.toml kept if DIR has one)
pondra login URL [--token T | --user U] keep a token for a server in ~/.pondra (PONDRA_HOME); asked for when not given
pondra branch [NAME] [--from prod] [--replace] [--drop] CREATE DATABASE NAME CLONE prod on prod’s node (NAME: the git branch’s); --drop lets it go
pondra diff [--against prod] the objects that differ and the rows of each table both have, as Markdown

Where the environment is:

Option Default Meaning
--env NAME, -e PONDRA_ENV, else the git branch’s database, else pondra.toml’s only environment the environment: [env.NAME] in pondra.toml, or a database on the project’s server
--url URL [env.NAME] url, else [project] server + /db/NAME the node to talk to
--lake LAKE [env.NAME] lake start a node for this lake instead
--token TOKEN PONDRA_TOKEN, else pondra login’s a token for it: an admin’s to deploy, a reader’s to plan
--project DIR . the project’s folder

A database name is letters, digits and _: pr-123 and feature/x become pr_123 and feature_x. A deploy sends [env.NAME]’s values ([env.dev]‘s for a database pondra.toml doesn’t name), the git commit, and [secrets]’ values from this machine’s environment.

pondra catalog --dir LAKE PREFIX prints the catalog’s entries whose keys start with PREFIX: t/ for tables, s/ for log segments, p/ for producers. It is for looking inside a lake, and what it prints is the lake’s own format: see the lake format.

Terminal window
pondra catalog --dir shell-lake t/ | cut -c1-40
t/orders {"columns":[["id","Int64"],["am