Backups
You will take a copy of Odudu's database, keep its encryption key beside it but separately, and check that the copy can be read. Everything Odudu knows is in one PostgreSQL database plus a few secrets in the server's environment, so that is all there is to back up.
The mechanism is pg_dump. Written backup and restore guidance, proven by an actual restore, is not done: it is planned work (roadmap, P12). Until then this page is what the project knows, not a promise that a restore works for your deployment. Restore into a scratch database once, so you find out before you need it.
What to keep
Keep two things, and keep them apart.
- The database. Every tenant's people, clients, keys, settings and audit trail are rows in the one database
ODUDU_DATABASE_URLpoints at. - The secrets.
ODUDU_KEKwraps every signing key and every console token stored in that database, andODUDU_CONSOLE_CLIENT_KEYis registered on every tenant. A dump restored without the sameODUDU_KEKholds keys nobody can unwrap, so the signing keys and the stored console sessions cannot be read. Keep both values in your secret store, in a different place from the dumps. A dump with its key beside it hands both to anyone who finds it.
The database roles are not in a dump, because roles belong to the PostgreSQL cluster. A restore into a new cluster needs them first, and needs the owner role to have the same name it had when the dump was taken: the dump records default privileges for that role by name, and the restore stops with role does not exist without it. Step 3 gives the commands.
1. Dump the database
Use the owner connection. The custom format is compressed and lets you restore parts of it.
pg_dump --format=custom --file=odudu-$(date +%F).dump "$ODUDU_DATABASE_URL"
On the quick start's stack, where Postgres is a container, the same dump runs inside it:
docker compose exec postgres pg_dump -U odudu --format=custom --file=/tmp/odudu.dump odudu
2. Check the copy reads
A dump that exists is not a dump that restores. List its contents, which reads the whole archive's table of contents.
docker compose exec postgres pg_restore --list /tmp/odudu.dump
You should see a line of TABLE DATA for each table.
3. Restore it into a new cluster
Do this once on a scratch PostgreSQL 17, so you know it works before you need it. The steps below were run end to end against a dump of the quick start's stack: the restored database served the system tenant's discovery document and signing keys to a server started with the same ODUDU_KEK. The project has not yet run a restore against a full sign-in, which is what the roadmap (P12) promises.
- As an administrator, create the owner under the name it had when you dumped (
oduduon the quick start's stack,odudu_ownerif you followed Deploying), the serving role and an empty database. Also createodudu_appand give it to the serving role. The migrations normally do this, but a restore brings the record that they already ran, so they never run again, and without the grant the serving role has no privileges while/health/readystill answers200.
CREATE ROLE odudu_owner LOGIN CREATEROLE BYPASSRLS PASSWORD '<owner password>';
CREATE ROLE odudu_svc LOGIN PASSWORD '<serving password>';
CREATE DATABASE odudu OWNER odudu_owner;
CREATE ROLE odudu_app NOLOGIN;
GRANT odudu_app TO odudu_svc;
- Restore the dump with the owner role, stopping at the first error.
pg_restore --no-owner --exit-on-error --dbname "postgres://odudu_owner:<owner password>@<host>:5432/odudu" odudu-2026-01-01.dump
- Start a server against the restored database with the
ODUDU_KEKandODUDU_CONSOLE_CLIENT_KEYyou kept, and check a tenant's keys are served.
curl -fsS https://id.example.com/tenants/system/protocol/openid-connect/certs
4. Keep the copy
Copy the dump off the machine that made it, on a schedule that matches how much you can afford to lose, and keep several.
What a backup does not cover
- Mail still waiting in the outbox goes back to the state it had when the dump was taken, so a restore may send a message that was already sent, or miss one queued after.
- The audit trail is a table like the rest, so a restore returns it to the moment of the dump. An export of it that you made earlier is yours to keep.