Skip to content
sloop

Backups

The backup key

Backups are encrypted to an age keypair, and the asymmetry is the whole design: taking one needs no secret, and only putting one back does.

When a registry takes its first backup, sloop makes it an age keypair. Every backup that registry takes from then on is encrypted to it — and the two halves of that pair live in completely different places, for completely different reasons.

Showing Linux. Change it in the strip above and every path and store name follows.

The asymmetry is the point

A symmetric key would mean the secret has to be present to write an encrypted backup — so a nightly job would need it unlocked, sitting somewhere a scheduler can reach, at three in the morning, unattended. That is the thing worth avoiding, and splitting the pair is what avoids it.

The public half

age1t6mzq0xv9k2wr5ldh8nj3cp7yfs4ueg0a2xk9msv4rzq3ltdn6shqw8cjp

Where it lives
In the registry, in plain sight. It is on the manifest of every backup it encrypted.
What needs it
Every backup. It is all a backup needs, which is why a scheduled run can take one at 3am with nothing unlocked and no secret anywhere near it.
What losing it costs
Nothing. It is derived from the private half and it is not a secret — publishing it costs you nothing at all.

The private half

AGE-SECRET-KEY-1K9XW3MT25G5TU6LTSN07M55JPT9Y0T24MDNQZ2DXW202RULF43W44CQ

Where it lives
In the Secret Service — or in the Argon2id-encrypted file, on a machine with no keyring running.
What needs it
Only a restore. Nothing else on this page, and nothing a schedule ever does, reads it.
What losing it costs
Every backup it ever encrypted, at once and for good. There is no recovery, no reset and nobody to ask.

So a scheduled backup holds nothing you would mind it holding, and the one thing worth protecting is read by exactly one command. That is the design, and everything below is a consequence of it.

The first backup will not run until you have a copy

A key that exists only inside one machine is not a backup strategy — it is a machine you cannot afford to lose. So sloop says this once, hard, and refuses rather than warning:

exit 2
$ sloop backup shop this registry has a new backup key age1t6mzq0xv9k2wr5ldh8nj3cp7yfs4ueg0a2xk9msv4rzq3ltdn6shqw8cjp backups from here on are encrypted to it. The private half is on this machine and nowhere else, so if this machine is lost, every one of those backups is unreadable — there is no recovery, no reset and nobody to ask. `sloop key export > backup-key.txt` writes it out. Keep that somewhere else.error: the backup key has not been copied anywhere yet, and there is no terminal to ask at hint: run `sloop key export` once, then this run will go through

On a terminal it asks and will take no for an answer — declining explicitly is a decision, and it only has to be made once. With no terminal to ask at, as above, it exits 2 and names the command that settles it. Nothing was dumped, nothing was written.

Exporting it

the run that makes the key
$ sloop key exporta new backup key for /home/you/projects/acme-api/.sloop public age1t6mzq0xv9k2wr5ldh8nj3cp7yfs4ueg0a2xk9msv4rzq3ltdn6shqw8cjp private the OS keyring, and on standard output below keep that line somewhere sloop cannot reach — a password manager, a safe. Every encrypted backup this registry takes needs it, and nothing else can replace it. AGE-SECRET-KEY-1K9XW3MT25G5TU6LTSN07M55JPT9Y0T24MDNQZ2DXW202RULF43W44CQ

The private key goes to standard output, deliberately, so that sloop key export > backup-key.txt is the whole of writing it down and the shell decides where it lands. Everything else on that block — the heading, the public key, the warning — goes to standard error, so the redirect captures the key and nothing else.

Somewhere sloop cannot reach

A password manager, a safe, a printed page. Not the machine sloop runs on, and not a disk it backs up — a key stored beside the backups it opens is one disk failure away from being neither a key nor a backup.

Running it again does not make a second key. The first word is the whole difference — a new backup key on the run that created one, the backup key every time after:

the same key, again
$ sloop key exportthe backup key for /home/you/projects/acme-api/.sloop public age1t6mzq0xv9k2wr5ldh8nj3cp7yfs4ueg0a2xk9msv4rzq3ltdn6shqw8cjp private the OS keyring, and on standard output below …

Moving to another machine

This is the whole reason export and import exist, and it is worth walking through in order — because the step in the middle is the one that surprises people.

1 · a backup on the old machine
# on the machine that has the backups$ sloop backup shopshop postgres://app@127.0.0.1:5444/shop✓ Connected postgres 17.9, 2 tables, 10601 rows✓ Dumped 2 tables 67.6 kB, encrypted✓ Checked 42958e81e612 backed up to /home/you/projects/acme-api/.sloop/backups/postgres/shop/20260919T142852Z 66.0 KiB in 0.3s, sha256 42958e81e612

Copy that backups directory to the new machine — any way you like, it is ordinary files — into the registry there. sloop finds it immediately and reads it perfectly:

2 · the new machine sees it
# on the new machine, after copying the backups directory across$ sloop backups listprojectshop postgres · 1 backup, 66.0 KiB 2026-09-19 20:28:52 +06:00 66.0 KiB, encrypted · 10601 rows 1 backup, 66.0 KiB · sizes checked, --check hashes them

Size, timestamp, row count, all of it. That is the manifest talking, which is plain JSON and not encrypted. The dump beside it is another matter:

3 · exit 5
$ sloop restore shop --confirm shoprestoring /home/you/projects/acme-api/.sloop/backups/postgres/shop/20260919T142852Z taken 2026-09-19 20:28:52 +06:00 (2026-09-19T14:28:52Z) 66.0 KiB of postgres 17.9, encrypted, 10601 rows, sha256 42958e81e612 into shop — postgres 17.9 it is emptyerror: that backup is encrypted to age1t6mzq0xv9k2wr5ldh8nj3cp7yfs4ueg0a2xk9msv4rzq3ltdn6shqw8cjp and this registry has no key hint: `sloop key import` takes the key exported from the machine that made it

It names the public key the backup was encrypted to, so you can tell which key you are looking for rather than guessing. This is what losing the key looks like from the inside: a listing that reads perfectly and a dump that opens for nobody.

4 · the key
$ sloop key import < backup-key.txtimported age1t6mzq0xv9k2wr5ldh8nj3cp7yfs4ueg0a2xk9msv4rzq3ltdn6shqw8cjp kept in the OS keyring, and backups under /home/you/projects/acme-api/.sloop are encrypted to it from now on
5 · the same command, now
$ sloop restore shop --confirm shoprestoring /home/you/projects/acme-api/.sloop/backups/postgres/shop/20260919T142852Z taken 2026-09-19 20:28:52 +06:00 (2026-09-19T14:28:52Z) 66.0 KiB of postgres 17.9, encrypted, 10601 rows, sha256 42958e81e612 into shop — postgres 17.9 it is empty loaded in 0.1s checking it against the counts the manifest recorded✓ Verified exact count(*) on both sides verifying — exact count(*) on both sides public.customers 1284 → 1284 public.orders 9317 → 9317 2 of 2 tables matched

Nothing about the backup changed between step 3 and step 5. The only difference is the Secret Service.

One key per registry, and sloop keeps it that way

Importing the key that is already there is a no-op, and it says so rather than pretending to work:

nothing to do
$ sloop key import < backup-key.txtalready the key here age1t6mzq0xv9k2wr5ldh8nj3cp7yfs4ueg0a2xk9msv4rzq3ltdn6shqw8cjp

Importing a different key into a registry that already has one is refused. Accepting it would silently make every backup already taken unreadable — the registry would encrypt new ones to the new key and have nothing left that opens the old ones:

a refusal that protects
$ sloop key import < a-different-key.txterror: this registry is already encrypting to age1t6mzq0xv9k2wr5ldh8nj3cp7yfs4ueg0a2xk9msv4rzq3ltdn6shqw8cjp, and that is a different key hint: every backup already taken can only be read with the key it was written for. To move to a new key, take the `[encryption]` block out of the registry by hand first — and keep the old key, or those backups are gone

The way out is deliberately manual, because it should be: take the [encryption] block out of the registry by hand, keep the old key, and let sloop make or take a new one. A flag for this would be a flag whose worst case is losing every backup you have, and --force is not offered here.

and a key that is not one
$ sloop key import < notes.txterror: that is not an age private key: invalid Bech32 encoding hint: it is the `AGE-SECRET-KEY-1…` line that `sloop key export` printed

What you lose, and to what

Two different losses, and the page is only worth reading if you leave able to tell them apart.

Losing the machine

Nothing, if you exported the key. The backups are ordinary files — copy them wherever they need to go. A new machine, sloop key import, and sloop restore works exactly as it did. That is steps 1 to 5 above, and it is the case sloop is built for.

Losing the key

Every backup it ever encrypted. Not damaged, not partially readable — the dumps are age ciphertext and nothing opens them. sloop cannot help, there is no recovery path, and there is nobody to ask: the private half was never anywhere but that one machine's keyring, which is the property that made the scheduled backup safe in the first place.

Which is why the refusal at the top of this page exists, why it is a refusal and not a warning, and why it fires before the first encrypted backup rather than after it.

Export it once, today. It takes one command and one paste into a password manager, it never has to be done again for that registry, and it is the difference between losing a laptop and losing everything that was ever on it.

The backups page is what those encrypted files are and how they are taken, pruned and put back. Every flag both pages mention is on the command reference.

sloop — a database operations CLI for PostgreSQL, MySQL and MariaDB. MIT or Apache-2.0.