Self-hosted personal nutrition tracker — single Docker container, AGPL-3.0, no telemetry, no cloud sync.
Svelte
243
98 commits
updated Sep 19, 2026
Trace Every Bite
A self-hosted personal nutrition tracker.
No accounts, no telemetry, no cloud sync unless you opt in.
Jump to: What it is · Features · Install · Env vars · Docs
iOS fund: the Trace apps have no iOS app yet, because building one needs a Mac and an iPhone. See the goal or chip in. Self-hosting stays free either way.
NutriTrace runs as a single Docker container on your own hardware, with a PWA for the browser and a native Android app for your phone. No accounts on external services, no data leaving your network, no subscriptions.

/api/v1/* scoped Bearer tokens for CT + LT + external clients. Full guide.MCP_ENABLED=1. Read-only in Phase 1. Full guide.Published to two registries with identical tag sets: ghcr.io/traceapps/nutritrace (primary) and traceapps/nutritrace on Docker Hub (mirror). The snippet below uses GHCR; swap in traceapps/nutritrace:latest if that suits your setup.
Minimum viable docker-compose.yml:
services:
nutritrace:
image: ghcr.io/traceapps/nutritrace:latest
container_name: nutritrace
ports:
- "3001:3001"
volumes:
- ./data/db:/data/db
- ./data/uploads:/data/uploads
environment:
- DB_PATH=/data/db/nutritrace.db
- UPLOADS_PATH=/data/uploads
- JWT_SECRET=change-me-to-a-long-random-string
# OIDC_ISSUER=https://auth.example.com # optional single-provider SSO
restart: unless-stopped
Generate a JWT secret:
openssl rand -base64 48
Start it:
docker compose up -d
Open http://localhost:3001 and the first-run wizard will walk you through user management and creating your admin account. Skipping user management runs the app in single-user mode.
Full compose recipes with SMTP, Docker secrets (*_FILE), reverse-proxy examples, and multi-provider OIDC at docs/getting-started/compose/. Tag policy (:latest, :1, :1.0, :1.0.0, :dev, legacy :1.0.0-rc.N) in DEPLOY.md.
Pre-release testers can grab the rolling dev-latest APK; occasional milestone builds also get numbered -devNN pre-releases. See DEPLOY.md for details.
| Variable | Required | Default | Description |
|---|---|---|---|
JWT_SECRET | If using users | Signing key for auth tokens. Long random string. | |
DB_PATH | Yes | /data/db/nutritrace.db | SQLite database file inside the container. |
UPLOADS_PATH | Yes | /data/uploads | Upload directory inside the container. |
PORT | No | 3001 | Container-side port the server listens on. |
BASE_URL | No | Subpath prefix when mounted behind a reverse proxy (e.g. /nt). | |
LOG_LEVEL | No | info | error | warn | info | debug | trace. |
TRACE_REQUEST_BODIES | No | unset | Set to 1 with trace logging to include redacted request bodies. |
TRACE_REQUEST_PATHS | No | /api/diary,/api/sync/push | Comma-separated body-trace path prefixes; * traces all and none traces no request bodies. |
TRACE_BODY_MAX_BYTES | No | 32768 | Maximum serialized bytes emitted for one traced request body. |
INSECURE_COOKIES | If on plain HTTP | unset | 1 drops the Secure cookie flag; needed only on plain-HTTP LAN. See docs/getting-started/lan-http/. |
MAX_SESSION_HOURS | No | 720 | Auth cookie lifetime. |
RECOVERY_TOKEN | No | Passphrase to disable user management from the login page (lockout recovery). | |
OFF_LOCAL_DB | No | Path to an Open Food Facts Parquet snapshot (or legacy DuckDB file) for an air-gap mirror. | |
USDA_API_KEY | No | Optional server-side USDA FoodData Central key (users can also enter their own in Settings). | |
MEALIE_URL | No | Base URL of a Mealie instance for recipe import. | |
MEALIE_API_KEY | No | Mealie API key. | |
AI_PROVIDER | No | Lock Trace to a provider: claude | openai | gemini | oai-compat. | |
OIDC_ISSUER | No | Enable a single OIDC provider by setting OIDC_ISSUER + OIDC_CLIENT_ID + OIDC_CLIENT_SECRET. Numbered form (OIDC_PROVIDER_2_*) for multi-provider. | |
SMTP_HOST | No | SMTP server for password reset & invites; also SMTP_PORT (587), SMTP_SECURE (false), SMTP_USER, SMTP_PASS, SMTP_FROM. |
Full list (Docker secrets *_FILE variants, air-gap OFF_LOCAL_ONLY, per-provider AI knobs, all OIDC_* options) at docs/self-hosting/env-vars/. SMTP and AI can also be set in the Settings UI; env vars take priority and lock those fields.
API request logs include a client/server correlation ID, method, path, response status, duration, outcome, and observed wire-body bytes. Uploaded images and frontend assets are not request-logged. Content-Length is reported separately as an untrusted declared_bytes value and is never substituted for bytes actually observed. Oversized JSON requests return HTTP 413 with request_id, observed size_bytes (or null), declared_bytes, and limit_bytes fields.
LOG_LEVEL=trace enables fine-grained internal trace calls, but request content remains off unless TRACE_REQUEST_BODIES=1 is also set. Minimal byte counting happens before body-size enforcement; body inspection happens only after parsing and access checks. Selected request bodies, including sync pushes, use bounded traversal with credential and setting-value redaction, URL sanitization, inline data: URL summaries, and the TRACE_BODY_MAX_BYTES output cap. Query strings are omitted. Bodies can still contain private diary and health data; enable body tracing only temporarily and do not publish the resulting logs without reviewing them.
The app sends X-Request-ID to the server and records the same ID in verbose client diagnostics. The server returns the ID on responses, so a failing app request can be matched directly to its server log. Invalid logging configuration fails at startup instead of silently falling back.
Two host directories bind-mount:
DB_PATH). SQLite file.UPLOADS_PATH). Food/meal photos and server-side backups (uploads/backups/).Nothing else needs to persist; the container is stateless beyond these two volumes.
docker compose pull
docker compose up -d
The database schema migrates automatically on startup.
| Layer | Technology |
|---|---|
| Frontend | Svelte 5 (compat mode), svelte-spa-router, Vite 6, PWA (service worker) |
| Mobile | Capacitor 8 (Android), @capacitor-community/sqlite, ML Kit barcode, Health Connect |
| Backend | Node.js, Express 5, better-sqlite3, optional DuckDB for the local OFF mirror |
| Auth | JWT (httpOnly cookie), bcryptjs, OpenID Connect 1.0 (PKCE + state + nonce) |
Part of the TraceApps family. Sister apps: CookTrace for recipes and pantry, LiftTrace for weightlifting. Full docs for all three at traceapps.github.io/docs.
NutriTrace is translated with Weblate, a free web platform for libre translation projects. No coding required — pick your language, translate the strings, submit. Missing keys fall back to English at runtime, so partial translations are safe to release.
Start translating at hosted.weblate.org/projects/nutritrace/. See CONTRIBUTING.md for domain-specific guidance (regulatory nutrition terms, proper nouns, tone).
NutriTrace is free to self-host and always will be. No paid tier, nothing behind a donation, no telemetry. It's built and maintained by one person.
The current goal is iOS. None of the Trace apps run properly on an iPhone, because building and testing for iOS needs Apple hardware, plus the developer accounts for both app stores. That comes to about $1,300, and the itemised breakdown is on the Support page.
Helping doesn't have to cost anything: starring the repo, reporting bugs with detail, and translating all count, and stars are how self-hosted projects get found.
NutriTrace was inspired by two self-hosted nutrition trackers:
NutriTrace is not medical, health, or nutrition-professional software. It does not provide medical advice, diagnosis, treatment, or personalized nutrition prescriptions. Food entries, calorie and macro tracking, Trace AI suggestions, Smart Log parsing, Scan Label output, Goal Insights, Adaptive TDEE recommendations, wellness scores, and any analytical output are for informational and self-tracking purposes only.
Nutrition decisions can interact with medical conditions (diabetes, eating disorders, food allergies, pregnancy, breastfeeding, pediatric needs, kidney or liver disease, metabolic disorders) in ways this app cannot assess. Consult a qualified healthcare professional, registered dietitian, or licensed nutritionist before starting a new eating plan, calorie target, or making significant dietary changes.
Trace AI answers can be incorrect or incomplete; treat them as a starting point, not a substitute for human judgment or professional advice. Food nutrition data from Open Food Facts is community-curated and may contain inaccuracies. Use at your own risk.
Svelte
51.9%
JavaScript
45.1%
Java
1.3%
Self-hosted personal nutrition tracker — single Docker container, AGPL-3.0, no telemetry, no cloud sync.
Svelte
243
98 commits
updated Sep 19, 2026
Trace Every Bite
A self-hosted personal nutrition tracker.
No accounts, no telemetry, no cloud sync unless you opt in.
Jump to: What it is · Features · Install · Env vars · Docs
iOS fund: the Trace apps have no iOS app yet, because building one needs a Mac and an iPhone. See the goal or chip in. Self-hosting stays free either way.
NutriTrace runs as a single Docker container on your own hardware, with a PWA for the browser and a native Android app for your phone. No accounts on external services, no data leaving your network, no subscriptions.

/api/v1/* scoped Bearer tokens for CT + LT + external clients. Full guide.MCP_ENABLED=1. Read-only in Phase 1. Full guide.Published to two registries with identical tag sets: ghcr.io/traceapps/nutritrace (primary) and traceapps/nutritrace on Docker Hub (mirror). The snippet below uses GHCR; swap in traceapps/nutritrace:latest if that suits your setup.
Minimum viable docker-compose.yml:
services:
nutritrace:
image: ghcr.io/traceapps/nutritrace:latest
container_name: nutritrace
ports:
- "3001:3001"
volumes:
- ./data/db:/data/db
- ./data/uploads:/data/uploads
environment:
- DB_PATH=/data/db/nutritrace.db
- UPLOADS_PATH=/data/uploads
- JWT_SECRET=change-me-to-a-long-random-string
# OIDC_ISSUER=https://auth.example.com # optional single-provider SSO
restart: unless-stopped
Generate a JWT secret:
openssl rand -base64 48
Start it:
docker compose up -d
Open http://localhost:3001 and the first-run wizard will walk you through user management and creating your admin account. Skipping user management runs the app in single-user mode.
Full compose recipes with SMTP, Docker secrets (*_FILE), reverse-proxy examples, and multi-provider OIDC at docs/getting-started/compose/. Tag policy (:latest, :1, :1.0, :1.0.0, :dev, legacy :1.0.0-rc.N) in DEPLOY.md.
Pre-release testers can grab the rolling dev-latest APK; occasional milestone builds also get numbered -devNN pre-releases. See DEPLOY.md for details.
| Variable | Required | Default | Description |
|---|---|---|---|
JWT_SECRET | If using users | Signing key for auth tokens. Long random string. | |
DB_PATH | Yes | /data/db/nutritrace.db | SQLite database file inside the container. |
UPLOADS_PATH | Yes | /data/uploads | Upload directory inside the container. |
PORT | No | 3001 | Container-side port the server listens on. |
BASE_URL | No | Subpath prefix when mounted behind a reverse proxy (e.g. /nt). | |
LOG_LEVEL | No | info | error | warn | info | debug | trace. |
TRACE_REQUEST_BODIES | No | unset | Set to 1 with trace logging to include redacted request bodies. |
TRACE_REQUEST_PATHS | No | /api/diary,/api/sync/push | Comma-separated body-trace path prefixes; * traces all and none traces no request bodies. |
TRACE_BODY_MAX_BYTES | No | 32768 | Maximum serialized bytes emitted for one traced request body. |
INSECURE_COOKIES | If on plain HTTP | unset | 1 drops the Secure cookie flag; needed only on plain-HTTP LAN. See docs/getting-started/lan-http/. |
MAX_SESSION_HOURS | No | 720 | Auth cookie lifetime. |
RECOVERY_TOKEN | No | Passphrase to disable user management from the login page (lockout recovery). | |
OFF_LOCAL_DB | No | Path to an Open Food Facts Parquet snapshot (or legacy DuckDB file) for an air-gap mirror. | |
USDA_API_KEY | No | Optional server-side USDA FoodData Central key (users can also enter their own in Settings). | |
MEALIE_URL | No | Base URL of a Mealie instance for recipe import. | |
MEALIE_API_KEY | No | Mealie API key. | |
AI_PROVIDER | No | Lock Trace to a provider: claude | openai | gemini | oai-compat. | |
OIDC_ISSUER | No | Enable a single OIDC provider by setting OIDC_ISSUER + OIDC_CLIENT_ID + OIDC_CLIENT_SECRET. Numbered form (OIDC_PROVIDER_2_*) for multi-provider. | |
SMTP_HOST | No | SMTP server for password reset & invites; also SMTP_PORT (587), SMTP_SECURE (false), SMTP_USER, SMTP_PASS, SMTP_FROM. |
Full list (Docker secrets *_FILE variants, air-gap OFF_LOCAL_ONLY, per-provider AI knobs, all OIDC_* options) at docs/self-hosting/env-vars/. SMTP and AI can also be set in the Settings UI; env vars take priority and lock those fields.
API request logs include a client/server correlation ID, method, path, response status, duration, outcome, and observed wire-body bytes. Uploaded images and frontend assets are not request-logged. Content-Length is reported separately as an untrusted declared_bytes value and is never substituted for bytes actually observed. Oversized JSON requests return HTTP 413 with request_id, observed size_bytes (or null), declared_bytes, and limit_bytes fields.
LOG_LEVEL=trace enables fine-grained internal trace calls, but request content remains off unless TRACE_REQUEST_BODIES=1 is also set. Minimal byte counting happens before body-size enforcement; body inspection happens only after parsing and access checks. Selected request bodies, including sync pushes, use bounded traversal with credential and setting-value redaction, URL sanitization, inline data: URL summaries, and the TRACE_BODY_MAX_BYTES output cap. Query strings are omitted. Bodies can still contain private diary and health data; enable body tracing only temporarily and do not publish the resulting logs without reviewing them.
The app sends X-Request-ID to the server and records the same ID in verbose client diagnostics. The server returns the ID on responses, so a failing app request can be matched directly to its server log. Invalid logging configuration fails at startup instead of silently falling back.
Two host directories bind-mount:
DB_PATH). SQLite file.UPLOADS_PATH). Food/meal photos and server-side backups (uploads/backups/).Nothing else needs to persist; the container is stateless beyond these two volumes.
docker compose pull
docker compose up -d
The database schema migrates automatically on startup.
| Layer | Technology |
|---|---|
| Frontend | Svelte 5 (compat mode), svelte-spa-router, Vite 6, PWA (service worker) |
| Mobile | Capacitor 8 (Android), @capacitor-community/sqlite, ML Kit barcode, Health Connect |
| Backend | Node.js, Express 5, better-sqlite3, optional DuckDB for the local OFF mirror |
| Auth | JWT (httpOnly cookie), bcryptjs, OpenID Connect 1.0 (PKCE + state + nonce) |
Part of the TraceApps family. Sister apps: CookTrace for recipes and pantry, LiftTrace for weightlifting. Full docs for all three at traceapps.github.io/docs.
NutriTrace is translated with Weblate, a free web platform for libre translation projects. No coding required — pick your language, translate the strings, submit. Missing keys fall back to English at runtime, so partial translations are safe to release.
Start translating at hosted.weblate.org/projects/nutritrace/. See CONTRIBUTING.md for domain-specific guidance (regulatory nutrition terms, proper nouns, tone).
NutriTrace is free to self-host and always will be. No paid tier, nothing behind a donation, no telemetry. It's built and maintained by one person.
The current goal is iOS. None of the Trace apps run properly on an iPhone, because building and testing for iOS needs Apple hardware, plus the developer accounts for both app stores. That comes to about $1,300, and the itemised breakdown is on the Support page.
Helping doesn't have to cost anything: starring the repo, reporting bugs with detail, and translating all count, and stars are how self-hosted projects get found.
NutriTrace was inspired by two self-hosted nutrition trackers:
NutriTrace is not medical, health, or nutrition-professional software. It does not provide medical advice, diagnosis, treatment, or personalized nutrition prescriptions. Food entries, calorie and macro tracking, Trace AI suggestions, Smart Log parsing, Scan Label output, Goal Insights, Adaptive TDEE recommendations, wellness scores, and any analytical output are for informational and self-tracking purposes only.
Nutrition decisions can interact with medical conditions (diabetes, eating disorders, food allergies, pregnancy, breastfeeding, pediatric needs, kidney or liver disease, metabolic disorders) in ways this app cannot assess. Consult a qualified healthcare professional, registered dietitian, or licensed nutritionist before starting a new eating plan, calorie target, or making significant dietary changes.
Trace AI answers can be incorrect or incomplete; treat them as a starting point, not a substitute for human judgment or professional advice. Food nutrition data from Open Food Facts is community-curated and may contain inaccuracies. Use at your own risk.
Svelte
51.9%
JavaScript
45.1%
Java
1.3%