Skip to content
Rowsafe
Docs

CLI reference

Every rowsafe command and flag, exit codes, environment variables and .rowsafe.json.

The rowsafe CLI talks to the Rowsafe API. It runs on macOS, Linux and Windows (Intel/AMD and ARM).

Install

No sudo needed:

curl -fsSL https://rowsafe.sh/cli | sh
rowsafe login

The script downloads the latest release for your computer (native on Apple silicon, even from a Rosetta terminal) and checks it against SHA256SUMS. It installs to /usr/local/bin if you can write there, else to ~/.local/bin, which it adds to your PATH (open a new terminal). Nothing else changes. Read the script.

  • Update: run the same command again.
  • A specific version: curl -fsSL https://rowsafe.sh/cli | ROWSAFE_VERSION=0.10.12 sh.
  • Another folder: ROWSAFE_INSTALL_DIR=~/bin. ROWSAFE_NO_MODIFY_PATH=1 leaves your shell profile alone.
  • Uninstall: rowsafe logout (revokes its key), then rm "$(command -v rowsafe)".
  • Windows: download rowsafe-windows-amd64.exe (or rowsafe-windows-arm64.exe) from the latest release, check it against SHA256SUMS, rename it rowsafe.exe and put it in a folder on your PATH. Everything works there except rowsafe hosts add (use WSL) and MySQL and MariaDB used directly, for now.

Log in with rowsafe login.

Help

rowsafe help            # the short overview
rowsafe help all        # every command
rowsafe help COMMAND    # one command, e.g. rowsafe help mark
rowsafe version

Flags go before or after the arguments, with one or two dashes (-yes or --yes).

Saved databases

pulse, fix, insights, top, activity, settings, tune and mcp also work on a PostgreSQL database you reach directly. Save it once, then use its name. Nothing is sent to Rowsafe (unless you turn on usage counts, which never include your data). See Pulse on any database.

CommandWhat it does
rowsafe add NAME [CONN]Check that the database answers (read-only) and save its connection string on this computer, readable only by you. Without CONN: piped in, --from-env VAR, or the project's DATABASE_URL (asks first). On the database's own server where Rowsafe runs on its own (sudo), it also offers to turn on backups, Marks, Proof and restores, and every command then takes the name. --no-check, --yes, --json.
rowsafe add NAME ssh://USER@HOSTSave a database on a server where Rowsafe runs on its own, reached over your own SSH: only the address is saved, the connection string stays on the server. Commands then take NAME and run on that server: status, mark, backups, restore, rewind, proof, alerts, channels, mcp through its agent, pulse, fix and the other direct ones with its rowsafe. --ssh USER@HOST, --database DB (its name on the server), --no-check, --yes, --json.
rowsafe use NAMEMake NAME this project's database: writes the name, never the connection string, to .rowsafe.json.
rowsafe databasesThe saved databases: name, host, database, user. Also rowsafe ls when you aren't logged in. --json.
rowsafe forget NAMEDelete a saved connection string. --yes.

Once, without saving: pass the connection string instead of NAME (rowsafe pulse postgres://user@host:5432/dbname), or set DATABASE_URL and leave it out (used when you aren't logged in).

Two commands work only this way, to help AI coding agents understand a database (both read-only):

CommandWhat it does
rowsafe schema [NAME] [TABLE...] [--table T] [--schema S] [--limit N] [--json]Tables with their columns, keys, constraints, indexes (and how often each was used), triggers, sizes; views, enums, extensions. One line per column. The 100 largest tables unless you name some (* matches); --limit 0 for all.
rowsafe explain [NAME] "SELECT ..." | -f FILE [--analyze [--timeout 30s]] [--json]The query's plan one step per line, what makes it slow in plain words, and the index that would likely help (CREATE INDEX CONCURRENTLY, not tested). Plans without running the query; --analyze runs a SELECT in a read-only transaction that is rolled back.

Logged in too, they work on a saved database, the project's, or DATABASE_URL.

Database names

[NAME] is found in this order:

  1. the NAME argument;
  2. ROWSAFE_DATABASE;
  3. "database" in the nearest .rowsafe.json (this directory or a parent);
  4. your organization's only database.

NAME can also be the database's ID. NAME without brackets is required.

Account

CommandWhat it does
rowsafe loginLog in with your browser (confirm the code shown). Saves a new API key.
rowsafe login --key rsk_...Log in with an existing API key (CI, scripts).
rowsafe login --url URLAnother API URL. Default https://api.rowsafe.sh.
sudo rowsafe login [--yes]On a server where Rowsafe runs on its own: also connects it to the dashboard, with its backups, Proofs and Marks.
rowsafe logoutForget the login and revoke the key login created. --keep-key keeps it valid.
rowsafe whoamiAPI URL, organization, plan and key in use.
rowsafe init [NAME] [--yes] [--no-agents]Write .rowsafe.json here, so this project uses NAME, and offer to add a short Rowsafe section to AGENTS.md and CLAUDE.md for AI agents.
rowsafe orgPlan, limits and usage.

The login is saved, readable only by you, in ~/.config/rowsafe/config.json (Linux) or ~/Library/Application Support/rowsafe/config.json (macOS).

Databases

CommandWhat it does
rowsafe lsYour databases.
rowsafe show [NAME]One database in detail.
rowsafe status [NAME]With a name: is it protected? Without: fleet health. --json for details.
rowsafe adopt NAMERegister an existing database (PostgreSQL, or another engine with --engine) and print a read-only plan. The installer does this for you.
rowsafe plan [NAME]Run the read-only plan again.
rowsafe apply [NAME]Apply the plan. Never restarts PostgreSQL.
rowsafe verify [NAME]Check now that changes reach your bucket (Rowsafe also does it once it notices the restart). Activates the schedules and queues the first full backup.
rowsafe restart NAMERestart PostgreSQL: a few seconds, open connections drop. Asks first; --yes skips.
rowsafe restart NAME --at TIMERestart later: 03:00 (the next one), "2026-10-20 03:00" (your time zone), "in 2h", 2d or RFC 3339, up to 30 days ahead. --at window: the next maintenance window. Asks first; --yes skips. --json. CLI 0.10.23 or later.
rowsafe restart NAME --cancelCancel the scheduled restart.

restart needs a server where install allowed restarts (can_restart is true, host online), and not Docker. Admins and owners only, never a read-only key, at most once every 2 minutes. A database in awaiting_restart then becomes active by itself. One scheduled restart per database: cancel it to pick another time.

adopt flags:

FlagDefault
--host HOSTHost name or ID. Optional when the organization has one host.
--port N5432The engine's port: PostgreSQL 5432 (names the Unix socket), MySQL and MariaDB 3306, MongoDB 27017, ClickHouse HTTP 8123, Redis and Valkey 6379, Qdrant REST 6333, Meilisearch 7700.
--engine ENGINEpostgresqlmysql or mariadb (guide), mongodb (guide), clickhouse (guide), redis or valkey (guide), qdrant (guide), meilisearch (guide). The installer sets up OpenSearch.
--socket-dir DIR/var/run/postgresqlPostgreSQL's Unix socket directory.
--retention-full N2Full backups to keep, 1 to 52 (MongoDB: 7, one a day).
--no-waitDon't wait for the plan.

NAME: 2 to 40 characters, lowercase letters, digits and dashes, starting with a letter.

apply flags: --yes skips the confirmation. --force replaces an archive_command or archive_library set by another tool. Use it only if you know what that tool did.

Databases and users

The databases, users and extensions inside a PostgreSQL server (guide). --on NAME picks the server (default: the name rules). Changes need an admin's or owner's read-write key.

CommandWhat it does
rowsafe db [ls]Databases (owner, size, connections, extensions) and users. --json for everything.
rowsafe db create DBA database owned by a new user of the same name; password shown once. --owner USER (an existing user), --new-owner USER (name the new one), --extension EXT (repeatable), --locale L, --template template0.
rowsafe db drop DBRemove a database and everything in it, after a Mark. You type the name; --yes skips.
rowsafe db usersUsers, the databases they can connect to, and how their password is stored.
rowsafe db user add USER --db DB[,DB]A new user; --access read_only, read_write (default) or owner. Password shown once. Redis and Valkey: --keys PATTERN instead of --db ("*" for every key). OpenSearch: --db takes indices and patterns (logs-*). Qdrant: an API key on the --db collections (none listed: every collection, for read-only and owner keys); --expires DAYS (admin keys always expire: 30 days by default, at most 90). Meilisearch: an API key on the --db indexes.
rowsafe db user password USERA new password, shown once.
rowsafe db user remove USERRemove a user; --reassign-to USER gets what it owns. Asks first; --yes skips.
rowsafe db ext on|off DB EXTENSIONTurn an extension on or off in one database. --allow-untrusted for extensions that can run programs on the server. vector, postgis and timescaledb are installed first where root allowed PostgreSQL updates (Extensions). timescaledb needs --restart unless already loaded: Rowsafe saves a Mark, then restarts PostgreSQL once.

--host ADDRESS on create, user add and user password sets the address in the printed connection string (default: the one your apps use). The password is encrypted on your server to a key the CLI makes in memory; Rowsafe only relays ciphertext.

Connect apps and psql

PostgreSQL only for now. Other engines: make a login or API key with rowsafe db user add (or in the dashboard) and use the engine's own client. --on NAME picks the server (default: the name rules).

CommandWhat it does
rowsafe env [DATABASE]Write the app's own database and login to .env as DATABASE_URL. DATABASE defaults to this directory's name (my-app becomes my_app). Creates the database, owned by a new user of the same name. --reset-password gives an existing user a new password instead (apps using the old one stop connecting; asks first, --yes skips).
rowsafe connect [DATABASE|SERVER]Open psql as a temporary user, removed when you quit (what it created goes to the database's owner). --access read_only, read_write or owner (default). --user USER connects as an existing user (psql asks its password). psql's own options go after --.

env flags: --file .env (the file), --name DATABASE_URL (the variable), --user USER (the new owner's name), --extension EXT (repeatable, new database only), --force (replace the variable if the file has it; otherwise nothing changes), --print (also print the connection string with its password).

  • The password never passes through Rowsafe and is printed only with --print. env makes it on your computer and sends only its SCRAM verifier. With an older agent, the server makes it and encrypts it for your terminal.
  • env writes the variable in place (a new file is readable only by you; an existing one keeps its mode). In a git repo it adds the file to .gitignore, and warns if git already tracks it.
  • connect needs psql (it says how to install it) and passes the password in psql's environment, never on a command line. On a Rowsafe Cloud server that doesn't let this computer connect yet, it offers to allow it first (--yes without asking).

Rowsafe Cloud

Rowsafe Cloud servers, with your login or a read-write key. NAME is a server's name or ID, or its database's name; default: the project's database, else the organization's only server. Anything that costs money or deletes asks first; --yes confirms in scripts.

CommandWhat it does
rowsafe cloud sizesDatabases and versions, then clouds, regions and sizes: CPUs, memory, disk, hourly price, the most a month costs, and what's sold out now. --cloud CLOUD, --region REGION, --json.
rowsafe cloud create NAMEA new server with PostgreSQL, MySQL, MariaDB, Valkey, ClickHouse, OpenSearch, Qdrant or Meilisearch, with backups, Proof and Pulse on. Default: the cheapest size free now, PostgreSQL 17, only this computer may connect. Shows size and price and asks first. The first hourly server gives you a link to add a card, once (an owner pays); it's created right after.
rowsafe cloud listServers Rowsafe created: status, size, price, who can connect. --json.
rowsafe cloud show [NAME]One server: status, size, price, where apps connect, who can connect, whether the backup passphrase is saved. --json.
rowsafe cloud wait [NAME]Follow a server being created or resized until ready. --timeout 30m.
rowsafe cloud resize [NAME] SIZEAnother size, after a Mark. The database is offline a few minutes while the server restarts; a pair with a standby switches over instead. Disks never shrink. Asks first; --yes, --wait.
rowsafe cloud allow [NAME] [me|IP|CIDR]...Let more addresses connect. Default: this computer (me). Asks before opening it to everyone.
rowsafe cloud firewall [NAME]Who can connect. Change it with --set A,B (the whole list), --add A, --remove A or --clear (nobody). Asks before cutting anyone off; --yes, --json.
rowsafe cloud passphrase [NAME]The backup passphrase, encrypted for this terminal only (Rowsafe can't read it). --file PATH writes a new file readable only by you. Rowsafe records that it was saved.
rowsafe cloud clone [SOURCE] NAMECopy a PostgreSQL, MySQL or MariaDB database to a new hourly server: as it is now, --at TIME or --mark LABEL. Default: next to the source, same size (--cloud, --region, --size to choose). --delete-after 24h (1 hour to 30 days) has Rowsafe delete it; otherwise it stays until you delete it. Real data, not masked.
rowsafe cloud retry [NAME]Set up again a server that failed. --wait.
rowsafe cloud delete [NAME]Delete the server and its database. You type the name (--yes in scripts). Refused until the backup passphrase is saved (clones aside). --with-standby deletes its standby too; --wait.

create flags:

Flag
--cloud hetzner, --region fsn1, --size smallWhere and how big. The region decides the cloud.
--enginepostgresql (default), mysql, mariadb, valkey, clickhouse, opensearch, qdrant or meilisearch. rowsafe cloud sizes lists what's offered.
--engine-version VPostgreSQL 15 to 18 (default 17), MySQL 8.4, MariaDB 11.8, Valkey 8, ClickHouse 26.3 or 26.8 (default 26.8), OpenSearch 3, Qdrant 1.19, Meilisearch 1.54.
--postgres 15|16|17|18Same as --engine postgresql --engine-version.
--allow me|IP|CIDRRepeatable. none: nobody connects until rowsafe cloud allow.
--standbyPostgreSQL, MySQL and MariaDB: a second server ready to take over. Doubles the price.
--extensions vector,postgis,timescaledbPostgreSQL 15 to 18: pgvector, PostGIS, TimescaleDB (Apache-2.0 edition), on in the postgres database and every new one (Extensions).
--wait, --yes, --jsonclone takes these and --allow too.
rowsafe cloud sizes
rowsafe cloud create shop-db --wait          # the cheapest size free now; only this computer may connect
rowsafe cloud create shop-mysql --engine mysql   # MySQL 8.4 (Intel and AMD sizes only)
rowsafe cloud create events --engine clickhouse  # ClickHouse 26.8 (sizes with 4 GB of memory or more)
rowsafe cloud create vectors --engine qdrant      # Qdrant 1.19 (sizes with 2 GB of memory or more; snapshots every 15 minutes)
rowsafe cloud create search-db --extensions vector,postgis   # pgvector and PostGIS on from the start
rowsafe env --on shop-db                     # DATABASE_URL in .env
rowsafe connect shop-db                      # psql, as a temporary user
rowsafe cloud allow shop-db 203.0.113.4      # let the app's server connect
rowsafe cloud resize shop-db medium
rowsafe cloud clone shop-db shop-test --at "10m ago" --delete-after 1d
rowsafe cloud passphrase shop-db --file shop-db-passphrase.txt
rowsafe cloud delete shop-db

Backups, Proof and Marks

CommandWhat it does
rowsafe backup [NAME]Back up now. --type full (default), diff or incr. --second-copy: to the second copy.
rowsafe backups [NAME]Backups, newest first. On a server on its own, --second-copy lists the second copy's.
rowsafe proof [NAME]Run Proof, the restore test, now. --second-copy restores from the second copy.
rowsafe secondcopy [NAME]The second copy: where it is, how far it is, its backups. --json.
sudo rowsafe secondcopy add [NAME] --endpoint HOST --bucket BOn a server on its own: test a second bucket, save its key ($ROWSAFE_REPO2_S3_KEY, $ROWSAFE_REPO2_S3_KEY_SECRET, or asked) and a new passphrase in /etc/rowsafe/agent.env, and turn it on. --endpoint also takes the bucket's URL. --region, --uri-style, --path-prefix, --ca-file, --yes, --no-wait.
sudo rowsafe secondcopy removeTurn it off; the bucket's content stays. --yes.
rowsafe proofs [NAME]Every Proof run and its result.
rowsafe mark [NAME] [LABEL]Set a Mark (a named restore point). LABEL defaults to manual-<UTC time>. --json prints the Mark (status, restore_from_backup, ...) for scripts and CI.
rowsafe marks [NAME]Marks, with the backup to restore from.
rowsafe rewind find [NAME]Find the moment rows were deleted or changed, or a table emptied or dropped. --table T, --since 24h or --from/--to, --kind, --json. Read-only.
rowsafe tasks [NAME]Recent tasks. --status S, --type T, --limit N (default 30).
rowsafe task IDOne task with its full log.
  • Commands that run something wait for it, and exit 1 if it fails. --no-wait on backup, proof and mark returns at once.
  • rowsafe mark X with one argument: X is the database if one has that name, else the label. So with .rowsafe.json naming app, rowsafe mark before-drop marks app as before-drop.

Files

Folders that go with a database, like uploads (guide).

CommandWhat it does
rowsafe files [NAME]Protected folders, status and last snapshot. --json.
rowsafe files add [NAME] PATHBack up the folder PATH on the database server with the database (docker:VOLUME for a Docker volume). --exclude cache,*.tmp leaves files out. On the server, it offers to give the agent read access (--yes).
rowsafe files remove [NAME] PATHStop backing it up (its snapshots age out). --yes skips the question.
rowsafe files backup [NAME]Snapshot the folders now. --no-wait returns at once.
rowsafe files snapshots [NAME]Each folder's snapshots, newest first. --folder PATH, --json.
rowsafe files set [NAME] --every 15mHow often to snapshot (5m to 24h). --keep-days N for a server's own folders (7 to 400).
rowsafe files restore [NAME] (--at TIME | --mark LABEL | --snapshot ID)Bring files back; by default only missing ones. --path REL (repeatable) for listed paths, --column DB:schema.table.column for files a column points to, --whole-folder (kept for undo), --overwrite to also replace changed files, --folder PATH for one folder, --into DIR to restore into another folder instead (a server on its own), --preview to only count. You type the database name unless --yes (not needed with --into).
rowsafe files undo [NAME]Undo the last whole-folder restore.
rowsafe files proof [NAME]Restore a few files and compare them with the live ones, now.

With CLI 0.10.23 or later, add --server SERVER (hostname or ID, see rowsafe hosts list) instead of NAME for a server's own files: any folder, or a Docker volume as /var/lib/docker/volumes/VOLUME/_data. For example rowsafe files add --server web1 /srv/app/storage or rowsafe files restore --server web1 --at "14:04". On the server itself, --server alone is this server (the default when it has no database). A restore or undo asks you to type the server's hostname; --column is for databases only.

Rewind

Same as the dashboard's Rewind page (guide). Owners and admins only, never a read-only key. Commands wait and print the result, e.g. Brought back 1,204 rows in public.orders and 3,310 in public.order_items.

CommandWhat it does
rowsafe restore [NAME] --to TIME|MARKRestore a copy next to production as it was at TIME or a Mark (rowsafe rewind copy in one flag). --hours N, --no-wait. On a server on its own, --second-copy restores from the second copy (when the first bucket is gone).
rowsafe restores [NAME]The copy, the recovery window and recent restores (as rowsafe rewind). --json.
rowsafe rewind [NAME]Recovery window, newest Marks, the copy, data kept aside by a rewind, and the next command to run. --json.
rowsafe rewind copy [NAME] (--at TIME | --mark LABEL)Restore a copy next to production (never touches production). --hours N keeps it N hours (default 24, 1 to 168). --no-wait returns at once.
rowsafe rewind compare [NAME] [TABLE...]Count, by primary key, rows missing in production, changed, and added since. No TABLE: every table. --db DB, --json.
rowsafe rewind rows [NAME] TABLE...Bring back rows missing in production from the copy, after a Mark. --include-changed also resets changed rows to the copy's values. --db DB. You type the database name; --yes skips.
rowsafe rewind extend [NAME]Keep the copy longer: --hours N from now (default 24, at most 168).
rowsafe rewind drop [NAME]Delete the copy. Asks first; --yes skips.
rowsafe rewind database [NAME] --to TIME|MARKRewind the whole live database (or --at TIME, --mark LABEL). A Mark first; PostgreSQL stops during the restore; current data is kept aside 7 days. Only on servers that allow restarts, also on its own. You type the database name; --yes skips.
rowsafe rewind undo [NAME]Undo the latest rewind (put the data from before it back). You type the database name; --yes skips.
rowsafe rewind cleanup [NAME]Delete data kept aside by a rewind, to free disk. The rewind can't be undone after. Asks first; --yes skips.
  • TIME, in your time zone: "2026-09-24 14:04", "2026-09-24 14:04:05", "14:04" (today), "10m ago", "2h ago", "1h30m ago", or RFC 3339 (2026-09-24T12:04:00Z). Not in the future.
  • TABLE: schema.table, or table for public.table. If the copy has several PostgreSQL databases: DB:schema.table or --db DB.
rowsafe rewind copy app --at "14:04"
rowsafe rewind compare app
rowsafe rewind rows app public.orders public.order_items

Move in

Move a database from a managed provider onto a server Rowsafe protects (guide). The source connection string is typed hidden, piped on stdin, or read from an environment variable, and encrypted on your machine to the server's key. Rowsafe never sees it.

CommandWhat it does
rowsafe migrate [NAME]Moves into NAME (or all) and their progress. --json.
rowsafe migrate start [NAME]Check the source, start copying into NAME's server, wait until caught up. --into DB (new database's name; default: the source's), --method live or dump (default: what the check recommends), --source-env VAR (connection string from $VAR), --id ID (check an existing move again), --host ADDR and --user NAME (with dump: the new connection string), --yes. Exits 1 when the check found something to fix.
rowsafe migrate status IDProgress, the check's findings, the result.
rowsafe migrate fix IDSet source tables without a primary key to REPLICA IDENTITY FULL, then check again.
rowsafe migrate switch IDSwitch over: the old database goes read-only (unless --writes-stopped), last changes arrive, sequences are set, row counts compared, and the new connection string is printed once. --user NAME, --host ADDR.
rowsafe migrate password IDA new password for the apps' login, printed once.
rowsafe migrate writable IDMake the old database writable again (rollback).
rowsafe migrate cancel IDStop, and remove Rowsafe's publication and replication slot from the old database. --drop-copy also deletes the partial copy.
rowsafe migrate finish IDForget the old database's connection string.

Standby

A standby is a readable second server, kept in sync, that takes over if the primary dies.

CommandWhat it does
rowsafe standby [NAME]The standby, its lag, fenced old primaries, automatic failover, and connection strings that follow the primary. --json.
rowsafe standby add [NAME] --host SERVERRestore the latest backup on an empty cluster of SERVER and follow the primary. --port N (which cluster, if several), --fingerprint F (confirm the server's key without asking), --no-stream (follow through the bucket only), --no-wait.
rowsafe standby promote [NAME]Make the standby the primary. The old primary is stopped for good first. --primary-down confirms it's down when its agent isn't reporting. --force promotes even if the standby misses some of its last changes. You type the name unless --yes.
rowsafe standby rebuild [NAME] [--host SERVER]Turn the fenced old primary into the new standby.
rowsafe standby unfence [NAME]Start the fenced primary again when the standby wasn't promoted.
rowsafe standby failover [NAME] --on|--offAutomatic failover. --after 3m: how long every condition must hold. --max-data-loss 1m: the most changes it may lose.
rowsafe standby remove [NAME]Remove the standby; its cluster gets its own data back.

To compare fingerprints before pairing, run sudo -u postgres rowsafe-agent key on the server.

On servers on their own (not connected to the dashboard; both set up with the same bucket and passphrase), each server runs its half (guide):

CommandWhat it does
sudo rowsafe standby link NAME --address IPOn the primary: let the standby at IP stream. Prints the command for the standby's server.
sudo rowsafe standby create NAME [--primary IP[:PORT]]On the standby's server: its empty cluster restores the backups from the bucket and follows the primary. On a fenced old primary, rebuilds it as the new standby. --port N (which cluster), --folder F (when link says so), --yes, --no-wait.
sudo rowsafe standby fence NAMEOn the primary: stop it for good for a switchover. The bucket records where its last change is.
sudo rowsafe standby promote NAMEOn the standby's server: promote it, after replaying the fenced primary's last change. --primary-down when the primary is down instead.
sudo rowsafe standby unfence NAMEOn a fenced primary: start it again, if the standby wasn't promoted (checked in the bucket).
sudo rowsafe standby unlink|remove|forget NAMEStop the streaming (primary), remove the standby (its server), let a fenced copy go.

Every change asks you to type the name unless --yes. standby failover is refused: there is no automatic failover on servers on their own.

Move to a new server

Moving a database is a standby on the new server, then a planned switchover.

CommandWhat it does
rowsafe move [NAME] --to SERVERSet up the new server and keep it in sync. --at TIME switches over then by itself; without it, it waits for move switch. --keep-days N keeps the old server (stopped) as a way back, 1 to 30, default 7. --port N, --fingerprint F, --no-wait as for standby add. Without --to: the move in progress.
rowsafe move switch [NAME]Switch over now. You type the name unless --yes.
rowsafe move schedule [NAME] --at TIMEChange when it switches over; --clear waits for move switch.
rowsafe move cancel [NAME]Cancel before the switchover; the database stays put.
rowsafe move back [NAME]Move back to the old server (its data is reused).
rowsafe move finish [NAME]Remove the old server: Rowsafe stops keeping it stopped.

Fork

A fork is a new, independent database cloned from another (now, any second of its recovery window, or a Mark), with its own backups.

CommandWhat it does
rowsafe fork [SOURCE] --to SERVERRestore SOURCE on SERVER as a new database and wait for its first backup. --to can be the source's own server (a new port). Without --at or --mark, it forks now, after a Mark named fork-<id>.
rowsafe forks [SOURCE]Forks of SOURCE, their status, and what SOURCE was forked from. --json adds each fork's masked columns.

fork flags:

Flag
--to SERVERHostname or ID. Without it, lists the servers and why any can't take the fork.
--name NAMEThe fork's name. Default SOURCE-fork.
--at TIMEA moment in the recovery window: "2026-09-24 14:04" (your time zone), "14:04", "10m ago" or RFC 3339.
--mark MARKA Mark instead of a time.
--maskMask personal data before anyone can connect.
--port NThe new cluster's port, 5440-5499. Default: a free one.
--into-port NUse this existing, empty cluster instead.
--fingerprint FConfirm another server's key fingerprint without asking (needed without a terminal).
--no-waitReturn once queued.
--jsonPrint the fork as JSON.

Database settings

rowsafe set [NAME] [--retention-full N] [--full-schedule CRON] [--diff-schedule CRON] [--proof-schedule CRON]

Only the flags you pass change. Schedules: 5-field cron in UTC, at most hourly. --proof-schedule sets when Proof runs. --diff-schedule "" turns differential backups off.

rowsafe set app --retention-full 4 --proof-schedule "0 4 * * 3"
CommandWhat it does
rowsafe remove NAMEStop managing a database. Never changes the server or the bucket. After a plan was applied, needs --keep-archiving (the server keeps archiving). --yes skips the confirmation.

Updates and upgrades

Only on servers where the installer allowed updates. Through MCP, only for an agent acting as an owner or admin. Security updates and reboots are fixes: rowsafe fix NAME. Guide: Update and upgrade PostgreSQL.

CommandWhat it does
rowsafe update [NAME]Show versions, then install PostgreSQL's newest minor release (e.g. 16.9 to 16.10) and restart, after a Mark. You type the database name; --yes skips, --no-wait returns once queued.
rowsafe update [NAME] --auto on|offInstall minor releases by itself on Sundays at 03:00 in --timezone ZONE (IANA, e.g. Europe/Berlin; default: your computer's).
rowsafe upgrade [NAME]Versions, newer majors, the latest rehearsal, and an upgrade that can be undone. --json.
rowsafe upgrade [NAME] --to 18Check, rehearse on a restored copy (unless a rehearsal to 18 passed in the last 14 days), then upgrade. --mode safe (default) or fast, --rehearse-only, --yes to skip typing the name.
rowsafe upgrade undo [NAME]Go back to the version the upgrade kept (for 7 days).
rowsafe upgrade cleanup [NAME]Remove the kept version to free disk; the upgrade can't be undone after.

Pulse: monitoring and alerts

CommandWhat it does
rowsafe alertsFiring alerts. --all adds resolved ones; --resolved shows only those.
rowsafe alerts ack IDAcknowledge a firing alert: no more reminders.
rowsafe alerts rulesBuilt-in alert rules with your organization's settings.
rowsafe pulse [NAME]Pulse: health score (0-100) and findings with what to do. Without NAME: every database, worst first. Exits 3 if one scores below 70. --json.
rowsafe fix [NAME] [FINDING [FIX] | NUMBER]Numbered fixes for what pulse found (clean up tables, refresh statistics, add, rebuild or remove an index, end a stuck session, remove an inactive slot, back up now...). In a terminal, asks which to apply. rowsafe fix app 2 applies number 2; rowsafe fix app FINDING FIX names it by ID. Asks before running; fixes that end a session or remove something ask you to type the database name. --yes skips. Same as Apply fix in the dashboard.
rowsafe insights [NAME]Largest tables and indexes, unused and duplicate indexes, estimated bloat, dead rows and vacuum, transaction ID age. --limit N, --json.
rowsafe top [NAME]Queries that took the most time, and which got slower. --since 24h (e.g. 1h, 168h), --sort total_time|calls|mean_time|rows, --query ID (one query over time), --json. Needs pg_stat_statements. On a server on its own, from the agent's readings (up to 14 days).
rowsafe recommendations [NAME]Recommendations (schema, queries, capacity, indexes): why, and what each costs. Without NAME: each database's top one. --group schema|queries|capacity|indexes, --dismissed, --json. --dismiss ID [--reason not_relevant|intended|later|wrong] [--note TEXT] sets one aside; --restore ID brings it back. Apply the ones Rowsafe can do with rowsafe fix.
rowsafe recommendations find [NAME]Test index ideas on a copy now (it also runs nightly) and wait; proven indexes appear in the Indexes group. --no-wait.
rowsafe recommendations schedule [NAME] auto|off|CRONWhen to test index ideas: auto (nightly at the quietest hour), off, or 5-field cron in UTC.
rowsafe reportThe "Your weekly Pulse" email: show settings, --preview (--html), --on/--off, --to A,B, --send-test.
rowsafe activity NAMEQueries running, or idle in a transaction, for over a minute.
rowsafe channels listNotification channels.
rowsafe channels add --type email --name NAME --address A [--address B]Email channel.
rowsafe channels add --type slack|discord|teams|webhook --name NAME --url URLSlack, Discord, Microsoft Teams or webhook channel. A webhook prints its signing secret once.
rowsafe channels add --type pagerduty --name NAME [--routing-key KEY] [--region us|eu]PagerDuty: the service's Events API v2 integration key.
rowsafe channels add --type opsgenie --name NAME [--api-key KEY] [--region us|eu]Opsgenie: an API integration's key.
rowsafe channels add --type telegram --name NAME [--bot-token TOKEN] --chat-id IDTelegram: a bot token from @BotFather and a chat ID (or @channel).
rowsafe channels add --type ntfy --name NAME --topic TOPIC [--server URL] [--token tk_...]ntfy: --server defaults to https://ntfy.sh. On a server without the dashboard, alerts go to Slack, Discord, ntfy or a webhook: Alerts on the server.
rowsafe channels remove IDRemove a channel.
rowsafe channels test IDSend a test notification now.

channels add takes --min-severity info|warning|critical (default warning). Teams, PagerDuty, Opsgenie, Telegram and ntfy need CLI 0.10.23 or later. Keys and tokens can stay off the command line: leave out --routing-key, --api-key or --bot-token to type it (not shown) or pipe it, or pass --secret-env VAR to read it from an environment variable. A flag another type takes is refused, not ignored. On a server without an account: Slack, Discord, ntfy (without --token) or webhook.

Connection pooling

Connection pooling with PgBouncer. Only where the installer allowed it (--allow-pooler), and not in Docker. Read-only keys can't change it. AI agents (MCP) change it as their person, if that person is an owner or admin.

CommandWhat it does
rowsafe pooling status [NAME]State, settings, direct and pooled connection strings, busiest pools. --json.
rowsafe pooling on [NAME]Install PgBouncer if needed and start it. If already on, changes the settings you pass and keeps the rest. Asks first; --yes skips.
rowsafe pooling off [NAME]Stop PgBouncer and remove what Rowsafe installed. Apps using it must connect directly again. Asks first; --yes skips.

pooling on flags (left out: Rowsafe picks, or keeps the current value):

FlagDefault
--mode transaction|sessiontransactionHow PgBouncer shares connections (which one).
--pool-size NFrom max_connections and CPUsPostgreSQL connections per database and user.
--max-client-conn N1000Client connections PgBouncer accepts.
--listen local|private|publiclocalThis server only; also its private network; or every address (only where root allowed --allow-pooler-public).
--port N6432PgBouncer's port.

Settings and tuning

Nothing restarts: settings that need a restart wait for rowsafe restart (or the next restart).

CommandWhat it does
rowsafe settings [NAME] [SETTING]PostgreSQL's settings that matter, by group: value, default, where it's set, whether a change needs a restart, and how many changes Rowsafe recommends. With SETTING: that one, explained. --all adds other settings from config files; --json.
rowsafe tune [NAME]Changes that suit this server's memory, CPUs and disk, each with why; applies them after asking. --workload web|analytics|mixed, --disk ssd|hdd (remembered), --all adds optional changes, --json prints and changes nothing. Tuning.
rowsafe settings set [NAME] SETTING=VALUE...A Mark, then ALTER SYSTEM and a reload. VALUE default resets one. Refused: Rowsafe's archiving settings and values PostgreSQL might not start with. --yes, --no-wait.
rowsafe settings undo [NAME] [CHANGE_ID]Undo the latest change made through Rowsafe (or CHANGE_ID). --yes.

Hosts

CommandWhat it does
rowsafe hosts listHosts with agent version and update state.
rowsafe hosts add [USER@]SERVERInstall the agent on a Linux server over SSH from this computer (macOS or Linux). One SSH login (-p PORT, -i KEY, or a ~/.ssh/config name), a one-time token that never appears in a command line, then the installer with its questions in your terminal (sudo unless root). Installer options after --. Without SERVER, on a Linux server, installs here.
rowsafe hosts enroll-tokenA one-time token and install command for a new host. --ttl (default 1h, 1 minute to 7 days).
rowsafe hosts pin HOST VERSIONKeep a host's agent on VERSION.
rowsafe hosts unpin HOSTFollow the host's update channel again.
rowsafe hosts channel HOST NAMESwitch the host's update channel (e.g. stable, beta).
rowsafe hosts remove HOSTRemove a host without databases and revoke its agent. --yes skips the confirmation.

API keys and audit

CommandWhat it does
rowsafe api-keys listAPI keys.
rowsafe api-keys create NAMECreate a key, shown once. --read-only for a key that can only read.
rowsafe api-keys revoke IDRevoke a key.
rowsafe auditWho changed what, newest first. --limit N (default 50).

Guard: AI agents

CommandWhat it does
rowsafe mcpMCP server on stdio, read-only. --allow-restore-points adds Marks. --allow-writes adds actions that don't touch production, plus changes to production made as you (request_change, create_cloud_server, cloud_firewall, apply_fix, create_app_database).
rowsafe guardThe Claude Code PreToolUse hook: sets a Mark before a destructive database command (Guard).
rowsafe guard --check COMMANDWhether COMMAND looks destructive.
rowsafe preview [NAME] FILEPreview a migration on a fresh copy. --db DB, --json or --format markdown, --fail-on careful|dangerous, --no-wait. FILE may be - (stdin). On a server on its own, on a full copy of its backups too.
rowsafe previews [NAME] [ID]Recent previews, or one in full.
rowsafe copies [NAME]Safe copies. copies create (--allow IP, --listen, --hours, --db, --masked (the default), --json; on a server on its own, --port and --listen localhost, its default), copies extend ID, copies delete ID, copies password ID (a new password, made on your machine).
rowsafe masking [NAME]Which columns safe copies mask. masking set [DB:]TABLE.COLUMN STRATEGY, masking refresh.

Usage counts

Off unless you turn them on. Rowsafe never asks, and with them off the CLI sends nothing about how you use it.

CommandWhat it does
rowsafe telemetry statusOn or off, and exactly what would be sent. --json for scripts.
rowsafe telemetry onSend anonymous usage counts after each command.
rowsafe telemetry offStop, and forget this computer's random ID.

When on, after each command the CLI sends one line to the control plane it uses (https://api.rowsafe.sh/v1/telemetry by default) with only these fields:

FieldExample
commandcloud create: the command's own names, never its arguments. Anything else is other.
version, os, arch0.10.19, darwin, arm64
exit_code0
durationA range: 0-1s, 1-10s, 10-60s, 1-10m or 10m+
terminaltrue when run from a terminal, false from a script or an AI agent
agentclaude-code, codex, cursor, gemini, or empty: found from variables only those agents set
logged_intrue or false
install_idA random ID made on this computer by rowsafe telemetry on, not tied to your account

Never sent: arguments, flag values, names of databases or servers, connection strings, hostnames, error messages, or anything that identifies you or your account. A separate process sends it once, without retries, so a command never waits for it or fails because of it. Rowsafe keeps the counts 180 days and doesn't keep the address they came from. ROWSAFE_TELEMETRY=0 or DO_NOT_TRACK=1 keep them off even after rowsafe telemetry on. The exact format is protocol/telemetry.go.

Tips

After a command that worked, the CLI sometimes prints one short, dimmed tip about something useful next (alerts on a server, a Mark before a risky change, the MCP server for your AI agent). At most one an hour, each at most every two weeks, and only at a terminal: never with --json, in a pipe, for an AI agent or in CI. Nothing is sent anywhere.

CommandWhat it does
rowsafe tips offNo more tips (ROWSAFE_NO_TIPS=1 too).
rowsafe tips onTips again.
rowsafe tips statusOn or off.

Exit codes

CodeMeaning
0Success. status: protected, or all healthy. guard --check: destructive.
1An error, a failed task, a cancelled confirmation. guard --check: not destructive.
2rowsafe without arguments. guard: blocked because protection is required. preview --fail-on: the migration fails on the copy.
3status: not protected, or the fleet has warnings or critical problems. pulse: a database scores below 70. preview --fail-on: the verdict is at or above it.

Environment variables

Variable
ROWSAFE_API_KEYAn API key instead of the saved login.
ROWSAFE_URLAn API URL instead of the saved one. Default https://api.rowsafe.sh.
ROWSAFE_DATABASEThe database when NAME is left out, and for guard.
DATABASE_URLA PostgreSQL connection string: when you aren't logged in, pulse, fix, insights, top, activity, settings, tune and mcp work on it directly (schema and explain use it even when you are), see Pulse on any database.
ROWSAFE_TELEMETRY0 keeps usage counts off, whatever rowsafe telemetry says. DO_NOT_TRACK=1 does the same.
ROWSAFE_REQUIRE_PROTECTIONFor guard: 1 or true blocks destructive commands when the database isn't protected. Overrides .rowsafe.json.

.rowsafe.json

Names the project's database for the CLI, the MCP server and the guard hook:

.rowsafe.json
{
  "database": "app",
  "require_protection": false
}
Field
databaseThe database's name in Rowsafe, or a name saved on this computer with rowsafe add.
require_protectionFor guard only: block destructive commands when the database isn't protected.

rowsafe init app (or rowsafe use app for a saved database) writes it here, keeping other fields. Commands look in this directory, then each parent. It holds no secrets: commit it.

rowsafe init also offers to tell the project's AI agents (Claude Code, Codex, Cursor) about Rowsafe: a short section in AGENTS.md and CLAUDE.md (a new AGENTS.md if there's neither) saying to run rowsafe status and set a Mark (rowsafe mark app before-<what>) before a migration, and rowsafe pulse for health. It only appends, between <!-- rowsafe:start --> and <!-- rowsafe:end -->, and never adds it twice. --yes adds it without asking, --no-agents leaves the files alone, --json lists what it did.

Edit on GitHub