Updating
TravStats follows semantic versioning and ships through GHCR with a Docker Hub mirror for stable releases. The update flow is two commands. Migrations run automatically.
Tags TravStats publishes
Section titled “Tags TravStats publishes”| Tag | What it points at | When to use |
|---|---|---|
:X.Y.Z | An exact released version (e.g. :2.6.0) | Immutable pin — you control upgrades manually |
:stable | Same image as the latest X.Y.Z final release | ”Production trunk” — promoted only after RC verification |
:latest | Same image as :stable | Default if you don’t pin in compose |
:rc-latest | The most recent Release Candidate (e.g. 2.7.0-rc.2) | Beta-test the next release on a side instance |
:X.Y.Z-rc.N | An exact RC build | Pin a specific RC — no auto-rollover |
Images are built to GHCR — ghcr.io/abrechen2/travstats — and the
final tags (:X.Y.Z, :latest, :stable) plus :rc-latest are
mirrored byte-identically to Docker Hub as abrechen2/travstats,
so Unraid, Synology and Portainer users can pull without switching
registries. Immutable RC and beta tags stay on GHCR only.
The standard update flow
Section titled “The standard update flow”cd /opt/travstats # wherever your docker-compose.prod.yml livesdocker compose -f docker-compose.prod.yml pulldocker compose -f docker-compose.prod.yml up -ddocker compose -f docker-compose.prod.yml logs -f appThat’s it. The entrypoint takes care of:
- Loading the persisted JWT secret from
/app/data/secrets/jwt.secret. - Auto-resolving any failed migrations from a previous boot
(
prisma migrate resolve --rolled-back …against the marker rows). - Running new migrations (
prisma migrate deploy). - Running idempotent backfill scripts (e.g.
backfillTimeSemantics.json the v1.2.0 → v1.3.0 boundary). Subsequent boots return zero rows and finish in milliseconds.
A successful upgrade ends with the [entrypoint] TravStats is ready
line and /health returning 200.
Pinning a version
Section titled “Pinning a version”If you want full control over upgrades — recommended for production:
# docker-compose.prod.yml (excerpt)services: app: image: ghcr.io/abrechen2/travstats:2.6.0Or via .env:
VERSION=2.6.0The compose file already references ${VERSION:-latest}, so setting
VERSION pins the tag without touching the YAML.
Before you upgrade
Section titled “Before you upgrade”Read the CHANGELOG first — every entry calls out:
- Breaking changes (rare; major versions only)
- Database migrations that run automatically (and what they do)
- New required env vars (almost never — most things move into the admin UI instead)
Take a backup, especially before any minor version bump. TravStats
writes backups to /app/data/backups on the interval you chose once
you’ve enabled the schedule (it is off by default), but a fresh dump
right before an upgrade is cheap insurance:
docker exec travstats-db pg_dump -U flights flights > flights-pre-upgrade.sqlUpgrading to 2.6.0
Section titled “Upgrading to 2.6.0”2.6.0 is the largest release so far. Three things to know before you pull it:
- 45 migrations run on the upgrade from 2.5.2. They add the lodging and places domains, two-factor and passkey tables, exchange-rate snapshots, the country-evidence tables and more, and one turns a flight’s duration into a column the database owns. On a large logbook the first boot takes noticeably longer than usual. Back up first; this is the release the paragraph above exists for.
- Your country count moves. Countries are now counted by the evidence that proves a visit — a completed stay, a recorded place, a port call, a day on the ground — rather than by where a flight touched down. The headline rises for accounts with cruises, places or hotel stays, and falls for accounts whose countries rested on airport connections, which are kept and greyed out but left out of the headline by default. The passport page says so once, with your real before-and-after figures. Nothing was lost; see Countries & passport and the troubleshooting entry.
- Take a fresh backup after upgrading. Backups taken before 2.6.0 carried only three of the six upload directories — trip photos, place photos and profile pictures were never in them — and cannot be repaired after the fact.
Other figures that move on the upgrade, all explained in the changelog: total flight time and hours in the air (placeholder clocks on date-only flights no longer count; estimates are labelled), airline counts (identity is now the IATA code), the top-routes list (a route is the pair, not the direction), and any total that used to add amounts across currencies.
Post-upgrade backfills
Section titled “Post-upgrade backfills”Most TravStats migrations are pure schema and run automatically during boot. A few releases also benefit from a one-shot data backfill that populates fields the new schema introduced. The scripts are idempotent — re-running them is safe and finishes in milliseconds when there’s nothing left to do.
v1.5.0 — backfillRouteDistance.ts
Section titled “v1.5.0 — backfillRouteDistance.ts”Pre-v1.5 versions left route_distance NULL on flights inserted
manually or via Excel import — only the provider-lookup paths
populated it. Result: total km, longest-flight, and the distance-tier
achievements were blank for those rows.
After upgrading to v1.5+, run the backfill once. Since 2.6.2 the
script is compiled into the image and runs with plain node —
the container has neither tsx nor the TypeScript sources, which is
why the earlier “copy the .ts file in and run it with npx tsx”
recipe could not resolve its imports. Count first, then apply:
docker exec travstats-app node /app/backend/dist/scripts/backfillRouteDistance.js --dry-rundocker exec travstats-app node /app/backend/dist/scripts/backfillRouteDistance.jstravstats-app is the container name from the shipped compose file;
use yours if you renamed it. On images before 2.6.2 the script is
present only as .ts and does not run inside the container — run it
from a source checkout against the database instead
(DATABASE_URL=… npx tsx backend/src/scripts/backfillRouteDistance.ts).
The script Haversine-computes distance from each flight’s already-
enriched departure / arrival coordinates and UPDATEs in batches of
500. It only touches rows where routeDistance IS NULL, so re-running
is a no-op. --dry-run counts rows without writing, --batch-size=N
tunes throughput; on a typical homelab instance, even ~200 rows
backfill in well under a second.
Rolling back
Section titled “Rolling back”If the new version misbehaves, roll back to the previous image tag before the database has accumulated new schema you can’t undo:
VERSION=2.5.2 # the previous version
docker compose -f docker-compose.prod.yml pulldocker compose -f docker-compose.prod.yml up -dPrisma migrations are forward-only by design — they don’t auto-revert when you downgrade the image. The new schema stays. If the failed upgrade introduced columns or tables the older code can’t tolerate — and 2.6.0 does, with a generated column and renamed stored values — you’ll need to restore the pre-upgrade SQL dump:
# Stop the app first so it can't write to the rolling-back DBdocker compose -f docker-compose.prod.yml stop app
# Drop and recreate the database from the dumpdocker exec -i travstats-db psql -U flights -d flights -c "DROP SCHEMA public CASCADE; CREATE SCHEMA public;"docker exec -i travstats-db psql -U flights flights < flights-pre-upgrade.sql
# Bring the (older) app back updocker compose -f docker-compose.prod.yml up -d appIn practice this is rare — most TravStats migrations are additive, and most “upgrade went wrong” cases recover by just downgrading the image tag. Treat a rollback across 2.6.0 as the exception and restore the dump.
Update banner inside the app
Section titled “Update banner inside the app”TravStats checks GitHub Releases every six hours and shows a pulsing
yellow Update badge in the header when a newer final release is
available (RCs and pre-releases are filtered out). Click for the
version, release date, a 600-character preview of the release notes,
and a link to the full notes. “Ignore this version” hides the
badge until something even newer ships.
If your instance is air-gapped or firewalled, the GitHub call fails silently — you just won’t see the badge. No errors are thrown.
Container image cleanup
Section titled “Container image cleanup”Old image tags accumulate in the local Docker storage. The narrow command removes dangling images and nothing else:
docker image prune -fdocker system prune is the wider one: besides unused images it also
deletes stopped containers, unused networks and the build cache.
On a host that runs only TravStats that is usually fine, but it is
not an image cleanup — read what it lists before confirming. Neither
command touches a running container or a named volume.