How the apps compare¶
CookTrace, LiftTrace, NoteTrace, and NutriTrace are siblings by design: same Node + Express 5 + better-sqlite3 + Svelte stack, same Docker layout, same reverse-proxy story, same auth and Trace-AI plumbing. The differences that matter to a self-hoster are small but real. This page collects them in one table so you can pick the compose file, port, and base image for each install without hunting through four READMEs.
At a glance¶
| Item | CookTrace | LiftTrace | NoteTrace | NutriTrace |
|---|---|---|---|---|
| Docker image | ghcr.io/traceapps/cooktrace |
ghcr.io/traceapps/lifttrace |
ghcr.io/traceapps/notetrace |
ghcr.io/traceapps/nutritrace |
| Base image | node:20-alpine |
node:20-alpine |
node:20-alpine |
node:20-slim (Debian) |
| Container port | 3003 |
3002 |
3004 |
3001 |
| Sample host port | 3003 |
3002 |
3004 |
3001 |
| DB mount | /data/db |
/data/db |
/data/db |
/data/db |
| Uploads mount | /data/uploads |
/data/uploads |
/data/uploads |
/data/uploads |
| Extra mount | none | none | none | /data/off-mirror (optional) |
| Auth cookie | ct_token |
lt_token |
note_token |
nt_token |
| Subpath env | BASE_URL=/cooktrace |
BASE_URL=/lifttrace |
BASE_URL=/notetrace |
BASE_URL=/nutritrace |
| Health endpoint | GET /api/health |
GET /api/health |
GET /api/health |
GET /api/health |
| Python bundled | yes (recipe-scrapers) | yes (better-sqlite3 build) | yes (better-sqlite3 build) | yes (better-sqlite3 build) |
| ffmpeg bundled | no | no | yes (audio only, about 5 MB: voice note conversion and splitting) | no |
| Android reminders | JS LocalNotifications |
JS LocalNotifications |
native exact alarms (AlarmManager) | native WorkManager |
| SMTP settings live in | Settings, Email | Settings, Email | Settings, Email | Settings, Authentication |
| Federation | pulls foods from NutriTrace; takes shopping items from NoteTrace | none | sends checklist items to CookTrace | serves foods to CT |
| App-only env vars | IMPORT_ZIP_MAX_MB |
EXERCISE_SOURCES, EXERCISEDB_OSS_URL, ALLOW_PRIVATE_RADIO_URLS |
ALLOW_PRIVATE_LINK_PREVIEWS, AI_TRANSCRIBE_MODEL, FFMPEG_PATH (see NoteTrace env vars) |
OFF_LOCAL_DB, OFF_LOCAL_ONLY, OFF_LOCAL_URL, API_RATE_LIMIT_PER_MIN, ALLOW_PRIVATE_IMAGE_URLS |
All four apps publish multi-arch images (linux/amd64 + linux/arm64) with the same two tags: latest for stable releases and dev for the development branch.
Why NutriTrace uses a Debian base¶
Alpine's musl libc cannot load the DuckDB native bindings, and NutriTrace uses DuckDB to query the optional Open Food Facts (OFF) local mirror (OFF_LOCAL_DB, roughly 7 to 8 GB Parquet). To keep a single image regardless of whether the mirror is on, the container is built on node:20-slim instead of node:20-alpine. Expect a slightly larger image in return for the OFF air-gap option. CookTrace and LiftTrace have no glibc-only dependency and stay on Alpine.
Why CookTrace bundles a Python interpreter¶
Recipe import from third-party sites goes through recipe-scrapers, a Python package with 300+ site-specific extractors. CookTrace's Dockerfile installs python3 plus recipe-scrapers, and the Node process shells out via PYTHON_BIN (default python3) when a scraper matches. LiftTrace and NutriTrace install python3 only as a build-time dependency for better-sqlite3 and never invoke it at runtime.
Android reminder backend¶
NutriTrace's Android build uses a native WorkManager worker for meal, water, weigh-in, and wind-down reminders (gated by the internal _USE_NATIVE_WORKER toggle). This survives WebView suspension and battery-optimisation kills better than the Capacitor LocalNotifications scheduler. CookTrace and LiftTrace still route reminders through the JS LocalNotifications plugin. If you run CookTrace and LiftTrace alongside NutriTrace and reminders occasionally miss on those two, this is why. NoteTrace schedules its reminders as native exact alarms.
NoteTrace's server-side reminders¶
NoteTrace is the only app that also delivers reminders from the server: a one-minute check sends each due note reminder through the user's push service and the reminder.fired webhook, alongside the Android app's on-device notifications. See NoteTrace reminders.
LiftTrace's radio LAN allowance¶
Only LiftTrace has a built-in radio player. From 1.4.0 the radio proxy works only for a signed-in account, checks every address a station's name resolves to and every redirect, and never reaches cloud-metadata addresses. An admin can play stations on your own network (a LAN Icecast); set ALLOW_PRIVATE_RADIO_URLS=1 to let every account, and a single-user install, play them. Your music server (Subsonic, Navidrome, Jellyfin, Plex, Emby) doesn't need it: those connections are open to every account, for the parts of its API the app uses. CookTrace and NutriTrace have no radio.
NutriTrace images from a link¶
From NutriTrace 1.4.0, a food, meal or profile photo given as a link is downloaded only after every address the name resolves to has been checked, every redirect is checked too, and cloud-metadata addresses are never reached. An admin may download from your own network (LAN, localhost, Docker networks); other accounts, and a single-user install (which has no sign-in), only with ALLOW_PRIVATE_IMAGE_URLS=1. Images from your saved CookTrace or Mealie never need it, and only real images are kept.
SMTP form location¶
CookTrace and LiftTrace put the SMTP form under Settings, Email. NutriTrace keeps it inside Settings, Authentication (same fields, same test-email button, just a different tile). Every env-lock and every save behaves the same way; the tile just lives one section over.