Files
HealthLog/docs/self-hosting/truenas.md
T
Marc-André Bombeck 308470f229 feat(encryption): back up the key, and refuse to serve when it cannot open the database
Every configured key id now has a canary in encryption_key_canaries,
checked at boot. A key that cannot open its canary, or existing data
written before the canary existed, puts the process in a refusal state:
every API route answers 503 encryption.key_mismatch, /api/health reports
reason encryption_key_mismatch, the pages render one explanation, and
neither the queue producer nor the worker starts. The process stays up
so the reason is visible instead of hiding in a crash loop.

The admin Encryption page gains a "Back up your encryption key" step:
key id and fingerprint (never the key), where the key lives per
platform, a check of a pasted copy that is compared and discarded, and
a confirmation bound to the active key id and fingerprint. The overview
gets a tile and admins get a dashboard banner while it is due.

HEALTHLOG_PLATFORM picks the platform the step opens on. Adds the
TrueNAS self-hosting page and key backup sections in the docs.
2026-10-03 18:06:28 +02:00

4.8 KiB
Raw Blame History

HealthLog on TrueNAS SCALE

HealthLog runs on TrueNAS SCALE as a Docker app: one app container from ghcr.io/mbombeck/healthlog and one PostgreSQL 16 database. The app container is stateless; everything you care about lives in the database dataset and in one value you must keep yourself: the encryption key.

What the app needs

Setting Value
Image ghcr.io/mbombeck/healthlog, tag X.Y.Z (amd64 and arm64)
Port 3000 (HTTP)
User runs as uid 1001; no extra capabilities, no-new-privileges is fine
Health check GET /api/health, 200 when ready
Start period at least 60 s, with more retries than the image's 3 × 30 s: migrations run at every boot
Database PostgreSQL 16 (17 and later are not a drop-in), no extensions
Volumes none for the app; the database gets its own dataset

Environment

Variable Required Notes
DATABASE_URL yes postgresql://<user>:<password>@<host>:5432/<db>
ENCRYPTION_KEY yes 64 hex characters: openssl rand -hex 32. Keep a copy outside the NAS (see below).
API_TOKEN_HMAC_KEY yes 64 hex characters, generated the same way. Changing it signs everyone out.
NEXT_PUBLIC_APP_URL yes the address people open, for example http://truenas.local:30180
APP_URL yes the same address
SESSION_COOKIE_SECURE no false for a LAN install served over plain http://; leave unset behind an HTTPS proxy
HEALTHLOG_PLATFORM no truenas: the admin's key backup step opens on the TrueNAS instructions
GEOLITE2_DIR no a mounted directory of GeoLite2 .mmdb files, so sign-in locations are resolved on the NAS

HEALTHLOG_PROCESS_TYPE stays unset (all): one container serves the web app and runs the background jobs.

Plain HTTP on the LAN

Most NAS installs are reached over http://. Set SESSION_COOKIE_SECURE=false for that. If it is left at its default, the browser drops the sign-in cookie and the login page says so before you type a password: "This server only sets its sign-in cookie over https://".

Back up your encryption key

HealthLog encrypts tokens, notes, documents and in-database backups with ENCRYPTION_KEY. The key is not stored in the database. If the app is deleted and reinstalled with a new key over the old database dataset, the data is still there but cannot be read.

  1. Copy ENCRYPTION_KEY from the app's settings (Apps, HealthLog, Edit, Environment) into your password manager.

  2. Sign in as the admin and open Admin, Encryption. The step shows the key id and a fingerprint, the first 12 hex characters of SHA-256 over the key. Compare it with your copy:

    echo -n "<your key>" | xxd -r -p | sha256sum | cut -c1-12
    

    or paste the copy into "Check my copy" (it is compared and dropped, never stored or logged).

  3. Confirm the backup. Until you do, admins see a reminder on the dashboard. After a key rotation the step comes back for the new key.

When the key does not match the database

At every start HealthLog checks that its key opens the data already in the database. If it does not, it refuses to serve instead of failing on every encrypted value: pages show "HealthLog cannot open its data", API requests answer 503 with meta.errorCode encryption.key_mismatch, /api/health answers 503 with reason: "encryption_key_mismatch", and no background jobs run. TrueNAS shows the app as unhealthy rather than crashed, so the reason stays visible.

Fix it one of three ways, then restart the app:

  1. Put the original ENCRYPTION_KEY back.
  2. Point DATABASE_URL at the database that belongs to this key.
  3. Start fresh with an empty database. The old data and its backups need the old key.

Updates

Pick the new tag, take a snapshot of the database dataset first, and update. Migrations run on start; the first start of a new install applies all of them, which is why the start period above is generous.