Before you start
- A terminal on the machine Postern runs on. Every command runs from the folder that holds
docker-compose.ymland.env. - Postern installed and running. Install Postern if it is not.
- The Console cannot back up. Settings → Your data has a Back up now row, and its
Back up button is disabled. The row says why:
Backup runs on the box — its status isn’t served to the Console.Backup runs on the machine Postern runs on, and the Console is never told how it went. - Postern never backs up on its own. You decide when to run this, and where the copy goes.
- These commands are for macOS and Linux. Postern runs under Docker, so it should also run on Windows. Nobody has tested that. This page has no PowerShell form.
Choose your path
Check who holds the master key
A backup is one file that holds two things: the master key, and the Postgres database. Neither works
without the other.Who holds the master key decides the next three steps. In the folder that holds
docker-compose.yml, run:An empty
PCI_MASTER_KEY= counts as 0. Postern reads an empty value as no value.Postern holds it. Postern makes a 32-byte key the first time it encrypts a credential. It is
not made at first boot. A Postern that has connected nothing has no key yet. Postern keeps the key
at /app/.pci/master.key, inside a Docker storage area named pci_master_key. The storage area
is the key.You hold it. The value after the = is the key, and the storage area holds nothing to copy.
Store that value in your password manager, away from the database copy. On the new machine, put
PCI_MASTER_KEY — and PCI_MASTER_KEY_ID, if you set one — into .env before Postern’s first
boot there.scripts/backup.sh in the repository does not work on the shipped Docker layout. It exits with
refusing a keyless backup and writes nothing.
Why, and what it does when you hold the keyCopy the master key out of the running container
With Postern running, in the folder that holds Check what you captured. On macOS:On Linux, the second line is
docker-compose.yml, run:sha256sum ./backup-staging/master.key.wc -c prints 32. Anything else means you captured something other than the master key.Write the checksum into your password manager, beside the name of the backup file you make in
step 5. You compare it at restore time. It proves a master key and a database copy came out of the
same backup file. Do not keep it only here — this is the machine you may lose.Read the master key when Postern is stopped
Skip this step if step 2 worked.If the
app container is not running — including after Postern refused to start — the same bytes
sit in the storage area. Docker puts your folder’s name in front of the storage area’s name, so
resolve the real name first:echo "$KEYVOL" prints a name that ends in _pci_master_key.Now run the two checks from step 2, and write the checksum down the same way.Copy the database
Postern can stay running for this. Optional: for a copy that nothing can change while it is made,
stop Postern first with
docker compose stop app, and leave the database running.In the same folder, run:-Fc writes the compressed format that pg_restore reads back. No password is needed.
WhyIf you changed POSTGRES_USER or POSTGRES_DB in .env, use your own values in place of pci.If you stopped Postern, start it again now:ls ./backup-staging prints master.key and pci.dump.Pack both halves into one file
In the same folder, run:
ls postern-backup-*.tar.gz prints one filename. It holds two files, and it is a backup only while
it holds both:docker compose down -v deletes the storage area that holds the master key. With a current backup
file, that is recoverable.Postern prints VAULT KEY IS NOT BACKED UP to its log on every boot — after a good backup, and
after a restore. It is a reminder, not an error, and no Console control clears it. To stop it,
write the marker file yourself or hold the key yourself:
the backup bannerCopy the backup file off the machine
A backup that lives only on the machine it protects is not a backup.To another computer you can reach over SSH:To an external drive: plug it in, then copy the file across with
cp or your file manager.Set the file to mode 0600 on the far side. Record where you put it in your password manager,
beside the checksum from step 2.Unpack the backup file on the new machine
The new machine needs Docker and the compose file first. Follow
Install Postern to the end of its step 2: install Docker, download
From an external drive, copy it across with On Linux, the last line is
docker-compose.yml into an empty folder, write a POSTGRES_PASSWORD line into .env. Compose
refuses to start without that password. Add back any other variables you had set. Do not start
Postern yet.Bring the backup file into that folder. From another computer that holds it:cp or your file manager. Unpack it, and check the
master key against what you recorded:sha256sum ./restore/master.key.wc -c prints 32, and the checksum equals the one in your password manager. A checksum that does
not match means the two halves come from different points in time. Postern will refuse to start
rather than half-work.Put the master key in place before the first boot
Skip this step if you hold the key. Put
PCI_MASTER_KEY, and PCI_MASTER_KEY_ID if you used one,
into .env instead.Boot is what opens the vault, so the master key must be in place before Postern’s first start. In
the folder that holds docker-compose.yml, run:docker compose create makes the container but does not start it. That creates the pci_master_key
storage area, so there is somewhere to put the key.Run docker compose ps and read the STATUS column of the db row. It says health: starting,
then healthy. The check runs every 5 seconds and gives up after 10 tries. A db row that never
reads healthy means Postgres did not start, and docker compose logs db says why.With the db row healthy, run:1000:1000 is the node user Postern runs as. Postern cannot read a key it does not own.Start Postern and open a real credential
In the same folder, run:Run The grep prints nothing, and
docker compose ps until the STATUS column of the app row reads healthy. Then run:/healthz answers {"status":"ok"}. That says Postern is running and
its database is reachable. It does not say the master key matches the vault. A
VAULT KEY IS NOT BACKED UP line is not a restore failure: the marker sat in the old volume and
the restore made a new one. Write it again.Now open a real credential:- Open the Console at
http://localhost:8787. If Postern runs on another computer, forward the port over SSH first. - In the left rail, click Sources.
- Click any connection except Apple Health. Apple Health is push-only: it has no Sync now button, and shows Pause receiving instead.
- On that connection’s page, under State, click Sync now.
- Its status reads
ok.
ok is the only
end-to-end proof that the master key and the database copy belong together. Delete ./restore, and
keep the backup file.SecretAuthError or MasterKeyError in the log means the master key and the database copy are
from different points in time. So does a refusal to start that names a key_id it cannot open.
Check PCI_MASTER_KEY_ID first: Postern looks rows up by that label, so the key can be right and
only its label wrong. Otherwise capture the master key again from the old machine and restore again. If
the old machine is gone and the key went with it, the data cannot be recovered.Export everything as one JSON file
In the Console at Only this computer can reach that address. Nothing on your Wi-Fi, and nothing on the internet, can.
From another computer, forward the port over SSH first.
Why the export answers here onlyThe file holds a
http://localhost:8787, go to Settings → Your data. Find the
Export everything row and click Export. It downloads one file named
postern-export-<date>.json.The same bundle is a plain GET. Run it on the machine Postern runs on:manifest and a data object.manifest— the export timestamp, the sectors that had rows, and a row count per object. It also lists your connections: connection id, provider, sector, status, last sync time.data— keyed by object:finance.transaction,health.workout, and for the sectors that hold a single object the sector’s own name,mail,calendar,contacts,home. It holds every live row in full, including therawpayload the source returned.
?sector=finance to narrow the bundle to one sector’s tables. The six sector names are
finance, mail, calendar, contacts, health and home. Any other name answers 400 with
unknown sector: <name>.Every export writes one row to Console → The ledger under the tool export. It carries the
sector if you narrowed it, and the number of rows, never the rows themselves.
What one row holdsChoose what to erase
In the Console at
http://localhost:8787, go to Settings → Your data. Find the
Erase data row and click Erase…. It opens its own page.Pick one of the three scope cards. Each states what it reaches before anything happens:- One connection —
Drop one source's cached rows.That connection stays connected, and a sync fills it again. - A sector —
Everything in one sector, all providers.Those connections stay connected. - Everything —
Every sector, every connection.Postern keeps its identity and your agent keys. The data is gone.
- Click Export first. The guard opens the moment you click, not when the download finishes — so watch that the file arrives.
- Type
ERASEinto the field labelled Type ERASE to confirm. - Click Erase this connection, Erase this sector or Erase everything.
Send the erase with the token Postern expects
The Console sends
ERASE for every scope. Postern checks the name of the thing you erase. So
an erase from the Console can fail with confirmation token does not match the requested scope.
Nothing was deleted when it does: Postern checks the token before it touches a row. The commands
below are the form that goes through.<connection-id>: the list on the Console’s erase page shows only the first 8 characters. For
the full value, open postern-export-<date>.json in a text editor, search for connections, and
copy the connectionId value.It answers with the scope, a count of the rows deleted from each table, and the total. Tables that
lost nothing are left out. So a short answer on a machine with a lot of data usually means you named
a narrower scope than you meant. A connection id that is not yours answers 404 and deletes
nothing.Running the same erase twice is safe. If an erase stops partway, run the same command again
to finish it. WhyConfirm it works
wc -con the capturedmaster.keyprints32.- The checksum you compute after you unpack the file equals the one in your password manager.
- The backup file holds both
master.keyandpci.dump. When you hold the key it holdspci.dumpalone, and you can produce thePCI_MASTER_KEYvalue from where you stored it. - The backup file also exists somewhere other than the machine Postern runs on.
- After a restore: a connection you clicked Sync now on reads
ok.
If something went wrong
What you have now
One backup file at mode0600, with a 32-byte master key and a database copy that restore only
together. A second copy of that file sits off this machine. An export is separate: a readable JSON
snapshot of your cached data, with no credentials in it and no route that reads it back.
If you erased, the rows are gone and the record says so. A sync brings each source back — except a
Plaid connection, which needs a new Link.
Next
Set up remote access
Tailscale, and Funnel only if a hosted agent needs you · about 20 minutes, once · off by
default, and both tiers can be taken back down.
Connect an agent
A running Postern and the Console in front of you · a few minutes, plus a restart of whatever
agent you connect · an agent key, shown once.