Skip to content

How the three apps compare

CookTrace, LiftTrace, 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 three READMEs.

At a glance

Item CookTrace LiftTrace NutriTrace
Docker image ghcr.io/traceapps/cooktrace ghcr.io/traceapps/lifttrace ghcr.io/traceapps/nutritrace
Base image node:20-alpine node:20-alpine node:20-slim (Debian)
Container port 3001 3003 3001
Sample host port 3000 3002 3000
DB mount /data/db /data/db /data/db
Uploads mount /data/uploads /data/uploads /data/uploads
Extra mount none none /data/off-mirror (optional)
Auth cookie ct_token lt_token nutritrace_token
Subpath env BASE_URL=/cooktrace BASE_URL=/lifttrace BASE_URL=/nutritrace
Health endpoint GET /api/health GET /api/health GET /api/health
Python bundled yes (recipe-scrapers) yes (better-sqlite3 build) yes (better-sqlite3 build)
Android reminders JS LocalNotifications JS LocalNotifications native WorkManager
SMTP settings live in Settings, Email Settings, Email Settings, Authentication
Federation pulls foods from NT none serves foods to CT
App-only env vars IMPORT_ZIP_MAX_MB EXERCISE_SOURCES, EXERCISEDB_OSS_URL, ALLOW_PRIVATE_RADIO_URLS OFF_LOCAL_DB, OFF_LOCAL_ONLY, OFF_LOCAL_URL, API_RATE_LIMIT_PER_MIN

All three publish multi-arch images (linux/amd64 + linux/arm64) with the same tag matrix: X.Y.Z, X.Y, X, latest, dev. Legacy X.Y.Z-rc.N tags stay pinned indefinitely.

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 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 all three and reminders occasionally miss on the two lighter apps, this is why.

LiftTrace's radio LAN allowance

Only LiftTrace has a built-in radio player. If you run a Subsonic, Navidrome, or Icecast server on the same LAN as LiftTrace, set ALLOW_PRIVATE_RADIO_URLS=1 so the radio proxy stops refusing RFC 1918 and IPv6 ULA addresses. CookTrace and NutriTrace have no equivalent knob because they have no radio.

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.