Install with Docker Compose¶
Docker Compose is the supported install path for CookTrace, LiftTrace, NoteTrace, and NutriTrace. One docker-compose.yml, one .env, docker compose up -d, done. All images are multi-arch (linux/amd64 and linux/arm64) and published to ghcr.io/traceapps/<app> (primary) and traceapps/<app> on Docker Hub (mirror). The examples below use GHCR; swap in the Docker Hub short form (e.g. traceapps/cooktrace:latest) if that fits your environment better.
On Unraid¶
All four apps are in Unraid's Community Applications. In the Apps tab, search for CookTrace, LiftTrace, NoteTrace or NutriTrace and install from there: the template sets the port, the appdata folders (/mnt/user/appdata/<app>/db and /uploads) and the settings. Fill in JWT Secret before the first start. Insecure Cookies starts at 1 so signing in works at the plain http:// address the WebUI link opens; set it to 0 once the app sits behind HTTPS (see Plain-HTTP LAN). The templates live in TraceApps/unraid-templates.
Prerequisites¶
- Docker Engine 24+ with the Compose plugin (
docker compose, not the legacydocker-compose). - Roughly 200 MB of disk for the container image, plus whatever your data grows to (SQLite database plus uploaded images).
- A long random string for
JWT_SECRET. Generate one withopenssl rand -base64 48.
Minimal compose file¶
Pick the tab for the app you're installing. Each snippet is a complete, working file. Save it as docker-compose.yml in an empty directory and run docker compose up -d from there.
services:
cooktrace:
image: ghcr.io/traceapps/cooktrace:latest
container_name: cooktrace
ports:
- "3003:3003"
volumes:
- ./data/db:/data/db
- ./data/uploads:/data/uploads
environment:
DB_PATH: /data/db/cooktrace.db
UPLOADS_PATH: /data/uploads
JWT_SECRET: change-me-to-a-long-random-string
env_file: .env
restart: unless-stopped
Container listens on 3003, exposed on host port 3003 (same number for convenience). Open http://localhost:3003 after the container is up.
services:
lifttrace:
image: ghcr.io/traceapps/lifttrace:latest
container_name: lifttrace
ports:
- "3002:3002"
volumes:
- ./data/db:/data/db
- ./data/uploads:/data/uploads
environment:
DB_PATH: /data/db/lifttrace.db
UPLOADS_PATH: /data/uploads
JWT_SECRET: change-me-to-a-long-random-string
restart: unless-stopped
Container listens on 3002, exposed on host port 3002 (same number for convenience). Open http://localhost:3002 after the container is up.
services:
notetrace:
image: ghcr.io/traceapps/notetrace:latest
container_name: notetrace
ports:
- "3004:3004"
volumes:
- ./data/db:/data/db
- ./data/uploads:/data/uploads
environment:
DB_PATH: /data/db/notetrace.db
UPLOADS_PATH: /data/uploads
JWT_SECRET: change-me-to-a-long-random-string
env_file: .env
restart: unless-stopped
Container listens on 3004, exposed on host port 3004 (same number for convenience). Open http://localhost:3004 after the container is up. NoteTrace is working toward its first release candidate; the image is published with that release.
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
env_file: .env
restart: unless-stopped
Container listens on 3001, exposed on host port 3001 (same number for convenience). Open http://localhost:3001 after the container is up. NutriTrace's image is node:20-slim (Debian) rather than Alpine because DuckDB's node bindings need glibc.
Host-port defaults are staggered
Defaults are chosen so every app can run on the same host without editing, and all avoid the very common :3000: NutriTrace on 3001, LiftTrace on 3002, CookTrace on 3003, NoteTrace on 3004. If any clash with something else on your host, change the left-hand port value in the mapping (e.g. "3010:3003" for CookTrace) or put everything behind a reverse proxy.
Picking an image tag¶
Each app publishes two tags:
| Tag | Updates when | Use case |
|---|---|---|
:latest |
Every stable release | Running the app |
:dev |
Every push to the dev branch |
Leading edge, not for production |
The compose files above use :latest. There are no version-number tags such as :1 or :1.4; to hold an install on one exact build, pin it by digest.
More detail in Docker image tag matrix.
Ports and volumes at a glance¶
| App | Container port | Suggested host port | DB volume | Uploads volume |
|---|---|---|---|---|
| NutriTrace | 3001 |
3001 |
/data/db |
/data/uploads |
| LiftTrace | 3002 |
3002 |
/data/db |
/data/uploads |
| CookTrace | 3003 |
3003 |
/data/db |
/data/uploads |
| NoteTrace | 3004 |
3004 |
/data/db |
/data/uploads |
Both volumes are plain bind mounts. Back them up with the same tool you use for the rest of your host (rsync, restic, borg, whatever).
Using an .env file¶
Anything past the required trio (DB_PATH, UPLOADS_PATH, JWT_SECRET) is easier to keep out of the compose file. Create .env next to docker-compose.yml:
JWT_SECRET=paste-openssl-rand-base64-48-output-here
TOKEN_ENC_KEY=paste-another-openssl-rand-base64-48-here
RECOVERY_TOKEN=one-more-random-string
SMTP_HOST=smtp.example.com
SMTP_PORT=587
SMTP_SECURE=false
SMTP_USER=user@example.com
SMTP_PASS=your-smtp-password
SMTP_FROM="TraceApps" <noreply@example.com>
CookTrace, NoteTrace, and NutriTrace include env_file: .env in their compose examples so every variable in .env reaches the container. LiftTrace works the same way once you add the env_file: line, or you can enumerate variables under environment:. See Environment reference for the full list.
For values you don't want on disk in plaintext (SMTP passwords, AI keys, OIDC client secrets), the container also accepts any variable name suffixed with _FILE. See Docker Secrets.
Healthcheck¶
None of the images include a built-in healthcheck. If you want one, add it yourself:
healthcheck:
test: ["CMD", "wget", "-qO-", "http://localhost:3001/api/health"]
interval: 30s
timeout: 5s
retries: 3
Adjust the port for the app: NutriTrace 3001, LiftTrace 3002, CookTrace 3003, NoteTrace 3004. The /api/health endpoint returns 200 OK with a short JSON body once the app finishes booting.
First-boot verification¶
After docker compose up -d, tail the container log until it stops moving:
You should see the app announce its listen port, run migrations, then go quiet. Open the app in a browser at the host port from the table above. First screen is the setup wizard, covered in First-run wizard.
If signing in over plain HTTP sends you back to the sign-in page (NutriTrace 1.4.0 and NoteTrace 1.1.0 and later say Signing in won't work on this address), jump to Plain-HTTP LAN (INSECURE_COOKIES). That's the single most common first-time issue.