Harbor

What runs

Every container on the box, what each one does, and which of them may talk to anything outside.

On this page

Outside the house

  • Your laptop or phoneover your tailnet, your own network, or a VPN
  • Your mailboxIMAP, read only
  • The model you chosetext of each document
  • Your backup targetencrypted snapshots, nightly

The box

  • tailscaleoptional: serves the app on your tailnettalks to your laptop or phone
  • webthe appinternal only
  • apiHTTP API, sessions, migrationsinternal only
  • workerOCR and text extraction; the only process that opens a documentno route out
  • suggestertitles, categories, dates, summariestalks to the model you chose
  • mailfetchreads connected mailboxestalks to your mailbox
  • backupnightly dump and snapshot, monthly restore testtalks to your backup target
  • postgres · redisstate and queuesinternal only

/data one encrypted volume: documents, database, queues, secrets

Nothing on the box listens on the public internet. Each container that talks outside talks to one thing.

The containers#

ContainerDoesNetwork
tailscaleOptional. Serves the app on your tailnet over HTTPSWhen present, the only thing reachable, and only from your tailnet. Without it, web publishes a port on the address you chose
webThe appNo host port of its own behind Tailscale; otherwise the published port
apiHTTP API, migrations, sessionsInternal, plus mail autodiscover during setup
workerOCR, thumbnails, text extraction; the only process that opens documentsNo route out
suggesterTitles, categories and dates from the text, via the model you chose or noneOut to that provider only
mailfetchReads connected mailboxes over IMAP; the only process that unseals mail passwordsOut to IMAP hosts only
backupNightly database dump and snapshot, monthly restore testOut to the backup target only
postgres, redisState and queues, on the encrypted volumeInternal

Isolation#

The OCR toolchain has a long history of vulnerabilities, and email-in lets strangers hand you a file. So the worker runs in its own container with no network except the database and queue, a read-only filesystem, no root, and a timeout per job. The API never opens a document.

Keys#

Volume passphrase, typed at boot ————— protects the powered-off box
  └─ master key, a file on that volume —— protects every document and every backup
       └─ a key per file ————————————— protects each document independently

Every document is encrypted under a key of its own, wrapped by the master key. The master key lives only on the encrypted volume and on the envelope. Rotating it re-wraps the per-file keys in seconds and never touches a document.

The database keeps each document's text in the clear so that search is fast, which is why the data volume must be encrypted and the installer refuses otherwise.

What it defends against#

SomeoneGetsWhy it is useless to them
A burglar who takes the boxA powered-off applianceThe volume is encrypted; the passphrase is never on the device
A breach at the backup providerSnapshotsEncrypted on the box before they leave; the key is only at home
An attacker on the internetNothing to connect toZero inbound ports on the tailnet path; on your own network, nothing unless you publish to the internet yourself
A malicious PDF sent by emailCode running in the OCR workerThe worker has no network and no root
Someone with a stolen family laptopA sessionTwo-factor required; sessions revocable

Not defended against, by design: a compromised running server, which holds plaintext; a malicious owner account; and the model provider, which sees document text. There is no end-to-end encryption between household members. The repository's security page states this in full.