Skip to content

Upgrading

Use this page as an upgrade runbook. The safe path is:

  1. Back up /data.
  2. Read the release notes for the version you are installing.
  3. Pull or change the image tag.
  4. Start the container and let migrations run.
  5. Verify login, Tracker, Admin status, backups, and bank sync.
  6. Keep the previous image tag and backup until the upgrade is confirmed.

The v0.36 → v0.37 notes are included below because that upgrade changes sessions, security settings, SimpleFIN matching, and React runtime behavior.

Before you upgrade

Do these before touching the running container:

  1. Open Admin → Backups and create a manual backup.
  2. Confirm the backup file appears in /data/backups.
  3. If this is production, copy the newest backup somewhere outside the app host.
  4. Confirm the host mount that contains /data/db/bills.db is persistent.
  5. Save the current image tag from docker ps.
  6. Review Current Release and any version notes between your current version and the target version.

Useful checks:

docker ps --filter name=bill-tracker
docker inspect bill-tracker-main --format '{{.Config.Image}}'
ls -lh /portainer/hosting/bill-tracker/data/db/bills.db
ls -lh /portainer/hosting/bill-tracker/data/backups

Upgrade a Docker install

If your Compose file tracks a tag such as latest, pull and restart:

docker compose pull
docker compose up -d
docker compose logs -f bill-tracker

If your Compose file pins a version, edit the image tag first:

image: dream.scheller.ltd/null/bill-tracker:dev-v0.37.0

Then start the new container:

docker compose up -d
docker compose logs -f bill-tracker

The Docker entrypoint runs migrations before the app starts. Leave RUN_DB_MIGRATIONS enabled for normal upgrades.

Upgrade a source install

For a direct source deployment:

git pull
npm install
npm run build
npm start

Restart your process manager after rebuilding. Migrations run when the server opens the database, so a manual node scripts/migrate-db.js is usually not required after a clean checkout.

Verify the upgrade

After restart:

  1. Open the app and log in.
  2. Open Admin → Status and check for database, migration, backup, and worker errors.
  3. Open Tracker as a regular user and confirm bills render.
  4. Open Bills and confirm a bill can be viewed/edited.
  5. If SimpleFIN is enabled, run or wait for a sync and confirm transactions appear.
  6. Run a manual backup after the upgrade.
  7. Keep the pre-upgrade backup until you are comfortable with the new version.

Rollback plan

The preferred rollback path is a known-good backup plus the previous image tag.

  1. Stop the container.
  2. Restore the pre-upgrade /data backup or use Admin → Backups if the app is still reachable.
  3. Change the Compose image tag back to the previous version.
  4. Start the container.
  5. Verify login and Admin → Status.

Migrations are versioned and recorded in schema_migrations. Some migrations have explicit rollback definitions in the Admin panel, but a full data backup is the recovery path to trust.

v0.36 → v0.37 Migration Notes

The following are operator-relevant changes that may affect deployed installations:

One-time re-login required

v0.37 hashes session tokens at rest (see Security below). Migration v0.94 deletes all existing plaintext sessions from the sessions table on first startup after this version. Every user — including the bootstrap admin — must log in again once.

Database migrations applied

Migration Adds Notes
v0.92 users.webauthn_enabled, users.webauthn_user_id, webauthn_credentials, webauthn_challenges WebAuthn / FIDO2 hardware security key support. Optional per user.
v0.93 bills.interest_accrued_month, payments.interest_delta, rewritten transactions.provider_transaction_id Interest now charged once per calendar month per debt bill. SimpleFIN transaction dedupe is now stable across disconnect/reconnect. Existing keys are rewritten; duplicates are deduped in-place.
v0.94 sessions.id switched to SHA-256(token), geolocation_enabled setting seeded false Session tokens hashed at rest; existing plaintext sessions are deleted. Geolocation becomes opt-in (off by default).
v0.95 subscription_catalog.subcategory, subscription_catalog.starting_monthly_usd, subscription_catalog.starting_annual_usd, subscription_catalog.price_notes, subscription_catalog_descriptors 1,501 rows (1,069 bank descriptors + 432 slang terms) seeded into descriptors. lookupCatalog now checks bank descriptors first.
v0.96 bills.catalog_id (FK → subscription_catalog.id), user_catalog_descriptors Catalog id backfilled for existing subscription bills via name matching. Per-user custom bank descriptors enabled.
v0.97 subscription_recommendation_feedback Captures accepts, declines, existing-bill links, and catalog relinks/unlinks as future learning signals.
v0.99 bills.autopay_verified_at, bills.inactive_reason, bills.inactivated_at, payments.autopay_failure Autopay trust indicator, cancellation reasons, retroactive autopay-failure flagging.

Security

  • Session token hashing (sessions.idSHA-256(token)) — see warning above.
  • IP geolocation is now a per-user opt-in via Profile → Privacy (geolocation_enabled, default false). No outbound ip-api.com calls are made for a user unless that user's toggle is on.
  • TOKEN_ENCRYPTION_KEY is honoured again. If the env var is set, all DB-key-encrypted secrets (SMTP password, OIDC client secret, SimpleFIN tokens, TOTP secrets, push notification tokens, login history IP/UA/location columns) are transparently re-encrypted with the env key in a single transaction on first startup. Migration is idempotent: already-e2: values are skipped. The Admin → Bank Sync card now shows which key source is active.
  • Rate limiting on sync/backfill endpoints (POST /:id/sync, POST /sync-all, POST /:id/backfill): 10 requests per 15 minutes per authenticated user. GET routes on the same router are unaffected.

Configuration changes

  • New env var: WEBAUTHN_RP_ID (default localhost) and WEBAUTHN_ORIGIN (default localhost) for WebAuthn / FIDO2 hardware key 2FA. Set these to your production domain in HTTPS deployments.
  • CSRF_HTTP_ONLY now defaults to true (was false in v0.34 and earlier). The SPA fetches the token from GET /api/auth/csrf-token and stores it in-memory; the server-side double-submit validation (header == cookie) is unchanged. If you pinned CSRF_HTTP_ONLY=false in your .env, leave it — the explicit override is still respected. Update Security Settings and Reverse Proxy and HTTPS to reflect the new default.

Behavioural changes

  • SimpleFIN lookback window: The Data page and admin Bank Sync card now show the actual backend values: a 44-day initial seed/backfill window and a 45-day hard limit (SimpleFIN Bridge's own cap). The previous inconsistent 90/89/6/60 UI numbers are gone.
  • Manual and auto-sync now produce identical match results: bankSyncService.runSync calls autoMatchForUser directly. The redundant call in bankSyncWorker was removed.
  • Subscription recommendations are now bank-backed known services only: Unknown recurring merchant patterns no longer mix into primary recommendations; high-confidence (90%+) bank transaction matches resolve to a known catalog entry. Recommendation confidence now separates identity, amount, and cadence evidence.
  • Subscription page actions simplified: Recommendation cards now show one clear primary action (Track Subscription or Link Existing Bill), a Details icon, and a compact More menu for secondary actions.
  • Subscription cadence sort: Tracked Subscriptions panel has a Custom/Cadence segmented control. Cadence groups by Weekly, Biweekly, Monthly, Quarterly, Yearly, and Other. Reordering is disabled while Cadence sort is active.
  • Manual sync and auto-sync: 30 s timeout (AbortSignal.timeout(30000)) on both claimSetupToken and fetchAccountsAndTransactions. Network errors and 5xx responses retry up to 3 times with 1 s / 2 s backoff; 4xx fail immediately.

React 19

The frontend was upgraded from React 18.3.1 to React 19. All 15 shadcn/ui primitive files had React.forwardRef(...) replaced with plain function components accepting ref as a named prop. BillModal was refactored to useActionState with a form action prop. Custom useOptimistic polyfill was removed in favour of the native React 19 hook. If you maintain a fork or downstream extension, audit any React.forwardRef usage you re-applied to primitives.

Each section above links to the user guide or admin page that documents the change. The most important starting points are:

See also

Next steps