Skip to main content
Before you start
  • A terminal on the machine Postern runs on. Every command runs from the folder that holds docker-compose.yml and .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

1

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 key
2

Copy the master key out of the running container

Take the master key from the container, never from a ./.pci/master.key file in your own folder. That file is a different key. Paired with a database copy from the container, it makes a backup file that restores cleanly and then decrypts nothing. Postern then refuses to start.
With Postern running, in the folder that holds docker-compose.yml, run:
Check what you captured. On macOS:
On Linux, the second line is 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.
3

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.
4

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.
5

Pack both halves into one file

Anyone who gets this file can read every password inside it. The lines below set mode 0600. Keep it that way.
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 banner
Never rotate the master key between step 2 and step 4. A database copy older than a rotation needs the older master key. Take a fresh backup immediately before any rotation, and keep the older backup file until every connection reads ok again. How a rotation and a backup drift apart
6

Copy 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.
7

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 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:
From an external drive, copy it across with cp or your file manager. Unpack it, and check the master key against what you recorded:
On Linux, the last line is 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.
8

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.
9

Restore the database

The first two lines destroy data. They drop and recreate the database. Run them only when you restore over an existing Postern. Nothing they delete is recoverable except from another backup file. On a fresh machine the database is already empty: run the third line alone.Every line ends in </dev/null on purpose. docker compose exec -T reads standard input. Without </dev/null it swallows the rest of what you pasted, and everything after it never runs. Run a multi-step restore from a saved script file, not from one paste.
pg_restore finishes and names no failed item.
10

Start Postern and open a real credential

In the same folder, run:
Run docker compose ps until the STATUS column of the app row reads healthy. Then run:
The grep prints nothing, and /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:
  1. Open the Console at http://localhost:8787. If Postern runs on another computer, forward the port over SSH first.
  2. In the left rail, click Sources.
  3. Click any connection except Apple Health. Apple Health is push-only: it has no Sync now button, and shows Pause receiving instead.
  4. On that connection’s page, under State, click Sync now.
  5. Its status reads ok.
A sync opens that connection’s encrypted credential. Every connection back at 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.
11

Export everything as one JSON file

In the Console at http://localhost:8787, go to SettingsYour 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:
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 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 the raw payload the source returned.
Rows the source itself has deleted are not in it. They are gone at the source, not your data.The export carries no credentials, no tokens and no access URLs. No route reads an export back in, so you cannot restore Postern from one. To undo an erase, reconnect the source and sync it again.Add ?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 holds
12

Choose what to erase

Erase deletes the rows themselves. It is not a mark the next sync undoes. There is no undo and no import: the export is a copy you can read, not a copy Postern can read back. One thing brings the data back — reconnect the source and sync it again.On a Plaid connection, not even that. Postern removes the Item at Plaid before it deletes a row. A later sync cannot resume; it needs a brand-new Link. That also stops the paid subscription those Items are billed under. The stop is best-effort, so check at Plaid afterwards if the subscription matters to you. An Item is one bank connection at Plaid; Link is Plaid’s connect-your-bank window. What Postern gets back from Plaid, and what it never sees
In the Console at http://localhost:8787, go to SettingsYour 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 connectionDrop one source's cached rows. That connection stays connected, and a sync fills it again.
  • A sectorEverything in one sector, all providers. Those connections stay connected.
  • EverythingEvery sector, every connection. Postern keeps its identity and your agent keys. The data is gone.
One connection is selected first, as the least destructive. For One connection or A sector, pick the target from the list under the cards. Then arm the erase:
  1. Click Export first. The guard opens the moment you click, not when the download finishes — so watch that the file arrives.
  2. Type ERASE into the field labelled Type ERASE to confirm.
  3. Click Erase this connection, Erase this sector or Erase everything.
If you change the target after that, both guards close again.At every scope the connector and credential rows survive, so you can sync again without a fresh connect. Plaid’s credential is the exception: Postern marks it dead. The record of the erase survives too, and it never held the row values. What one row holdsErase is not disconnect, and it is not revoke. The three are separate acts
13

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.
Run these on the machine Postern runs on, or through the SSH tunnel you use for the Console. Take the body for your scope.One connection:
One sector:
Everything:
For <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. Why

Confirm it works

  • wc -c on the captured master.key prints 32.
  • The checksum you compute after you unpack the file equals the one in your password manager.
  • The backup file holds both master.key and pci.dump. When you hold the key it holds pci.dump alone, and you can produce the PCI_MASTER_KEY value 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 mode 0600, 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.