sloop never asks for a connection twice. You register a database — a name, a connection and where its password comes from — and every command after that takes the name. sloop backup shop, sloop restore shop, sloop query shop.
The set of databases sloop knows is a registry. There is one global registry per machine and as many project registries as you make, which is what the second half of this page is about.
Registering one
Two ways, and they produce the same record. Give the whole connection as a URL, or give the fields one at a time — useful when a password manager holds the pieces separately, or when a host name has a character you would rather not escape.
$ sloop db add shop --global --url postgres://app@127.0.0.1:5442/shop --test✓ postgres 17.9 not encryptedregistered shop in the global registry (--global), password from the OS keyring postgres://app@127.0.0.1:5442/shop--test is the reason the engine line comes first: it opens the connection before saving anything and refuses to save a record that does not work. Without it the record is written as given and the first backup is where you find out.
$ sloop db add ledger --global --engine postgres \$ --host 127.0.0.1 --port 5442 --database ledger --user appregistered ledger in the global registry (--global), password from the OS keyring postgres://app@127.0.0.1:5442/ledger The name is yours — ledger above is filed under that name while the database on the server is also called ledger, and neither has to match the other. Renaming says what happens when they stop matching.
The password is asked for on a terminal and never appears in the command. It is not in argv, so it is not in ps, and it is not in the registry either — only the route to it is. That is the next section.
It does not guess, and it does not prompt for the connection
With neither --engine nor --url, db add stops and names both. The menu asks those questions; the command does not, because a command that prompts is a command that hangs in a scheduled run.
$ sloop db add nowhere --globalerror: no engine was given hint: pass --engine, or give the whole connection with --url postgres://user@host:5432/databaseMySQL and MariaDB
The same command, a different scheme. postgres, mysql and mariadb are the three sloop speaks, and MariaDB is its own engine rather than a MySQL setting because it ships mariadb-dump where MySQL ships mysqldump.
$ sloop db add sessions --global --url mysql://app@127.0.0.1:3311/sessions --test✓ mysql 8.4 over TLSregistered sessions in the global registry (--global), password from the OS keyring mysql://app@127.0.0.1:3311/sessionsover TLS is not something sloop turned on. It reports what the connection actually negotiated, and it says not encrypted just as plainly when that is the truth — which is what the PostgreSQL blocks above say.
Where the password lives
A registry holds a route, never a value. There is no spelling of a password a registry will accept, which is what makes it impossible to put one there by accident. Four routes, one flag each:
| Flag | Where it lives | For |
|---|---|---|
--keyring | the Secret Service | the default, and what you want unless something below applies |
--encrypted-file | XChaCha20-Poly1305, key from Argon2id | a headless machine with no keyring running |
--env <VARIABLE> | an environment variable, read every run | automation, and CI in particular |
--password-from <COMMAND> | whatever that command prints | a team password manager |
$ sloop db add headless --global --url … --encrypted-fileregistered headless in the global registry (--global), password from the encrypted file $ sloop db add nightly --global --url … --env SHOP_PASSWORDnote: SHOP_PASSWORD is not set, and the registry says the password is in it — registered anyway, because the environment variable SHOP_PASSWORD is read on every run rather than kept hereregistered nightly in the global registry (--global), password from the environment variable SHOP_PASSWORD $ sloop db add vaulted --global --url … --password-from "op read op://vault/db/password"note: the password command exited with 1: op read op://vault/db/password — registered anyway, because the command `op read op://vault/db/password` is read on every run rather than kept hereregistered vaulted in the global registry (--global), password from the command `op read op://vault/db/password` Both notes are the point rather than a warning to fix. A variable that is not set yet and a command that is not installed yet are both fine at registration time, because neither is read until the run that needs it — so SHOP_PASSWORD=… sloop backup nightly works on a machine where nothing was stored.
db list shows every record and the route beside it. It is the one command here you can run with somebody watching:
$ sloop db list --globalaccounts postgres://app@127.0.0.1:5442/ledger global · the OS keyringheadless postgres://app@127.0.0.1:5442/shop global · the encrypted filenightly postgres://app@127.0.0.1:5442/shop global · the environment variable SHOP_PASSWORDsessions mysql://app@127.0.0.1:3311/sessions global · the OS keyringshop postgres://app@127.0.0.1:5442/shop global · the OS keyringvaulted postgres://app@127.0.0.1:5442/ledger global · the command `op read op://vault/db/password` A bare name is looked for in the global store at /home/you/.sloop.Creating one, rather than registering one
db create is for a database that does not exist yet. It makes the database and the role that owns it, grants that role what a backup needs, then registers the result — so the account sloop uses afterwards is one that was made for the job rather than the superuser you happened to have.
$ sloop db create reports --global --engine postgres \$ --host 127.0.0.1 --port 5442 --database reports --role reports_owner \$ --superuser postgres --superuser-password-command "op read op://vault/pg/root" \$ --role-password-stdincreating postgres://reports_owner@127.0.0.1:5442/reports as postgres, whose password is used once and kept nowhere✓ postgres 17.9 not encryptedcreated reports role reports_owner created granted USAGE, CREATE ON SCHEMA public TO reports_owner granted default privileges on tables and sequences to reports_owner registered as reports in the global registry (--global), password in the OS keyringThe superuser password is used once and kept nowhere. The new role's password is the one that gets stored, and sloop generates it when you do not supply one — so the credential sloop holds from then on is a credential nobody has ever seen in a shell.
Two passwords, one standard input. When both would have to be typed and there is no terminal to type at, it says which flag is missing rather than hanging:
$ sloop db create reports --global … --superuser-password-stdinerror: there is no terminal to ask at, and --role-password-stdin is missing hint: unattended, it looks like this: --superuser-password-command "op read op://vault/pg/root" --role-password-stdin, with the new password piped in. Only one of the two can use standard input, so the other takes a --…-password-commandChecking and changing what is registered
db test opens the connection and reports what answered — engine, version, and what is securing the link. With no name it does that for every registered database, which is the quickest way to find the one that broke.
$ sloop db test accounts --global accounts postgres://app@127.0.0.1:5442/ledger global✓ postgres 17.9 not encrypted $ sloop db test sessions --global sessions mysql://app@127.0.0.1:3311/sessions global✓ mysql 8.4 over TLSA password route that cannot be followed fails here too, and fails loudly. A command that is not installed is not a connection problem, so the message names the command rather than the host:
$ sloop db test vaulted --global vaulted postgres://app@127.0.0.1:5442/ledger global✗ the password command exited with 1: op read op://vault/db/password hint: 'op' is not recognized as an internal or external command,; operable program or batch file.db edit changes a field without re-registering. It prints the old connection and the new one, in that order, so what changed is visible rather than implied:
$ sloop db edit ledger --global --host db.internalchanged ledger in the global registry postgres://app@127.0.0.1:5442/ledger postgres://app@db.internal:5442/ledgerRenaming, and the name on the server
db rename changes the name sloop files it under. The database's own name on the server does not change, is not contacted, and does not need to match — they are two different names and sloop never conflates them.
$ sloop db rename ledger accounts --globalrenamed ledger → accounts the password is filed under the connection, so it did not moveEverything keyed to that database follows the rename: its backups move with it on disk, and its schedule and its activity history stay attached. The password does not move, because it is filed under the connection rather than the label — which is also why renaming does not make you type it again.
After that rename, db test accounts reports postgres://app@127.0.0.1:5442/ledger. That is not a mistake in the listing: accounts is what you type and ledger is what the server calls it.
Forgetting one, and destroying one
Two commands that sound similar and are not remotely the same. db remove forgets a record. db drop destroys a database on a server.
$ sloop db remove shop --global --yesforgetting shop postgres://app@127.0.0.1:5442/shop the global registry, and the password kept in the OS keyring the database itself is not touched — that is `sloop db drop`forgot shopThat third line is printed every time, before the question, because the difference is worth stating rather than assuming. Removing takes the password out of the Secret Service as well — a credential for a database sloop no longer knows about is a credential nobody is going to remember to delete.
db drop is the destructive one, and it is typed rather than clicked. What you type is the database's own name on the server, not the label — because the label is what sloop calls it and the name is what is about to stop existing.
$ sloop db drop reports --globalabout to drop reports postgres://reports_owner@127.0.0.1:5442/reports postgres 17.9, registered here as reports 0 table(s), 0 row(s) — all of it nothing is kept and this cannot be undone — `sloop backup reports` first if you want a copy? type reports to destroy it, or anything else to stop: reportsdropped reports reports is still registered and now points at nothing — `sloop db remove reports` forgets it Note the last line: dropping does not remove the record. The name stays registered and now points at nothing, and db remove is what forgets it. Two commands, two decisions.
From a scheduled run there is no terminal to type at, so it refuses — and --confirm is the only thing that answers it. --yes does not, and is not consulted:
$ sloop db drop reports --global # from a scheduled runerror: dropping a database needs its name typed, and there is no terminal to type at hint: pass --confirm reports to say it up front $ sloop db drop reports --global --confirm wrongerror: --confirm says wrong, and the database is reports hint: nothing was contacted and nothing was changed. The two have to match exactly--confirm has to match exactly, which is the property that makes it safe in a file: a scheduled line names the database it destroys and cannot be repointed at a different one by editing a single flag.
Registries, global and project
Everything above went into the global registry — one per machine, the one a bare name uses when nothing else applies. sloop init makes a project registry in a directory, and a project's databases belong to that directory rather than to the machine.
$ sloop init _ | | ___| | ___ ___ _ __ / __| |/ _ \ / _ \| '_ \ \__ \ | (_) | (_) | |_) | |___/_|\___/ \___/| .__/ | | |_| Project registry created registry /home/you/projects/acme-api/.sloop name acme-api reachable anywhere as: sloop -C acme-api global /home/you/.sloop .sloop ignores itself, so git will never see it. Two things that line records. The project has a name, taken from the directory, and that name reaches it from anywhere on the machine as sloop -C acme-api — so a scheduled backup does not have to know where the checkout is. And .sloop ignores itself, so a project registry inside a repository never shows up in git status.
Which registry a bare name means
Five rules, checked in order, and the first one that matches wins. This is the whole of it — there is no sixth case and no per-command exception.
So a name registered in both belongs to the project, because rung four is reached before rung five. sloop does not leave that to be worked out — it says so in the listing and names the rung it took when it registers:
$ cd /home/you/projects/acme-api$ sloop db add shop --url postgres://app@127.0.0.1:5442/shop_stagingregistered shop in the project registry (the nearest .sloop at or above the working directory), password from the OS keyring postgres://app@127.0.0.1:5442/shop_staging $ sloop db listshop postgres://app@127.0.0.1:5442/shop_staging project · the OS keyringaccounts postgres://app@127.0.0.1:5442/ledger global · the OS keyringshop postgres://app@127.0.0.1:5442/shop global, shadowed · the OS keyring A bare name is looked for in this project, then the global store at /home/you/.sloop.global, shadowed is the answer to the question this page exists for. The global shop is still there, still backed up, still restorable — it is just not what a bare shop reaches while you are standing in this directory. The footer line underneath says where a bare name is looked for, in order, from wherever you happen to be.
And the shadowed one is one prefix away:
$ sloop db test shop shop postgres://app@127.0.0.1:5442/shop_staging project✓ postgres 17.9 not encrypted $ sloop db test global:shop global:shop postgres://app@127.0.0.1:5442/shop global✓ postgres 17.9 not encryptedglobal:shop forces rung one for a single name, and --global forces it for the whole command. Neither has to be spelled out from inside the project — both work from anywhere, and so do the two that reach the other way:
# standing anywhere else — neither of these needs the project directory$ sloop db list -C acme-api … the project registry, exactly as above $ SLOOP_PROJECT=acme-api sloop db list … the same thing. -C outranks it when both are givenWhere the store lives
/home/you/.sloop — the same path on all three platforms, spelled the way each one spells it. A store left behind in an older location is moved there once rather than ignored, so an upgrade does not quietly strand a registry.
A project's store is .sloop inside the project directory: /home/you/projects/acme-api/.sloop for the one above.
The registry is a database, not a file
It lives in the PostgreSQL that sloop setup prepares — sloop's own, on its own port, holding the registry, the schedules and what the background service has seen. You can open it yourself, and sloop tells you how:
$ sloop server connection sloop's own database answering Host 127.0.0.1 Port 5433 Database sloop_database User sloop_db_admin Password kept in the OS keyring URL postgres://sloop_db_admin@127.0.0.1:5433/sloop_database Open it with psql, DataGrip, or anything else that speaks PostgreSQL. `sloop server connection --show-password` prints the password on this terminal. Nothing about that is sealed. It is an ordinary PostgreSQL on an ordinary port, and psql, DataGrip or anything else that speaks the protocol will read it — and it still holds no passwords, only the four routes to them, for exactly the reason the listing further up holds none.
The flags on this page are the ones worth explaining. Every flag each of these commands takes is on the command reference, which is generated from --help rather than written beside it.