SparkyFitness
| Description / name | Input element |
|---|---|
| Container Registry | |
| Container Configuration Root Path | |
| Timezone | |
| User ID | |
| Group ID | |
| SparkyFitness /uploads Path | |
| SparkyFitness /backups Path |
Self-hosted privacy-first fitness tracker on FreeBSD.
| Registry | ghcr.io/daemonless/sparkyfitness |
| Daemonless | daemonless/sparkyfitness |
| Source | CodeWithCJ/SparkyFitness |
| Website | github.com/CodeWithCJ/SparkyFitness |
Version Tags
| Tag | Description | Best For |
|---|---|---|
latest |
Upstream Binary. Built from official release. | Most users. Matches Linux Docker behavior. |
Root Privileges Required
Podman on FreeBSD currently requires root. All commands must be run as root (or via doas/sudo).
Before deploying, ensure your host environment is ready. See the Quick Start Guide for host setup instructions.
Deployment
services:
sparkyfitness:
image: "ghcr.io/daemonless/sparkyfitness:latest"
container_name: sparkyfitness
environment:
- PUID=1000 # User ID for the application process
- PGID=1000 # Group ID for the application process
- TZ=UTC # Timezone for the container
- NODE_ENV=production # Node runtime mode; leave as 'production'
- SPARKY_FITNESS_DB_HOST=127.0.0.1 # PostgreSQL host the backend connects to; leave as 127.0.0.1 (host networking)
- SPARKY_FITNESS_DB_PORT=5433 # PostgreSQL port; MUST match the sidecar's POSTGRES_PORT. Default 5433 (NOT 5432) so this can coexist with another host-networked Postgres (e.g. Immich on 5432) on the same host. ⚠ With network_mode: host, two Postgres on the same port silently collide — keep each service on a distinct port. NOTE: this only takes effect if the daemonless/postgres image honors POSTGRES_PORT (see project README / upstream fix); otherwise the sidecar falls back to 5432.
- SPARKY_FITNESS_DB_NAME=${SPARKY_FITNESS_DB_NAME} # PostgreSQL database name
- SPARKY_FITNESS_DB_USER=${SPARKY_FITNESS_DB_USER} # PostgreSQL superuser
- SPARKY_FITNESS_DB_PASSWORD=${SPARKY_FITNESS_DB_PASSWORD} # PostgreSQL password (from secrets.env)
- SPARKY_FITNESS_APP_DB_USER=${SPARKY_FITNESS_APP_DB_USER} # Limited app DB role the backend creates during migrations (from secrets)
- SPARKY_FITNESS_APP_DB_PASSWORD=${SPARKY_FITNESS_APP_DB_PASSWORD} # Password for the limited app DB role (from secrets)
- SPARKY_FITNESS_API_ENCRYPTION_KEY=${SPARKY_FITNESS_API_ENCRYPTION_KEY} # 64-char hex encryption key (from secrets.env)
- BETTER_AUTH_SECRET=${BETTER_AUTH_SECRET} # Auth session signing secret (from secrets.env)
- SPARKY_FITNESS_FRONTEND_URL=${SPARKY_FITNESS_FRONTEND_URL} # Public URL of the frontend for CORS
- SPARKY_FITNESS_SERVER_HOST=127.0.0.1 # Internal bind address for the node backend; leave as 127.0.0.1 (nginx proxies to it)
- SPARKY_FITNESS_SERVER_PORT=3010 # Internal node backend port; leave as default
- SPARKY_FITNESS_LOG_LEVEL=ERROR # Backend log verbosity (e.g. ERROR, INFO, DEBUG)
volumes:
- "/path/to/containers/sparkyfitness/uploads:/uploads"
- "/path/to/containers/sparkyfitness/backups:/backups"
restart: unless-stopped
DIRECTOR_PROJECT=sparkyfitness
PUID=1000
PGID=1000
TZ=UTC
NODE_ENV=production
SPARKY_FITNESS_DB_HOST=127.0.0.1
SPARKY_FITNESS_DB_PORT=5433
SPARKY_FITNESS_DB_NAME=${SPARKY_FITNESS_DB_NAME}
SPARKY_FITNESS_DB_USER=${SPARKY_FITNESS_DB_USER}
SPARKY_FITNESS_DB_PASSWORD=${SPARKY_FITNESS_DB_PASSWORD}
SPARKY_FITNESS_APP_DB_USER=${SPARKY_FITNESS_APP_DB_USER}
SPARKY_FITNESS_APP_DB_PASSWORD=${SPARKY_FITNESS_APP_DB_PASSWORD}
SPARKY_FITNESS_API_ENCRYPTION_KEY=${SPARKY_FITNESS_API_ENCRYPTION_KEY}
BETTER_AUTH_SECRET=${BETTER_AUTH_SECRET}
SPARKY_FITNESS_FRONTEND_URL=${SPARKY_FITNESS_FRONTEND_URL}
SPARKY_FITNESS_SERVER_HOST=127.0.0.1
SPARKY_FITNESS_SERVER_PORT=3010
SPARKY_FITNESS_LOG_LEVEL=ERROR
options:
- virtualnet: ':<random> default'
- nat:
services:
sparkyfitness:
name: sparkyfitness
options:
- container: 'boot args:--pull'
oci:
user: root
environment:
- PUID: !ENV '${PUID}'
- PGID: !ENV '${PGID}'
- TZ: !ENV '${TZ}'
- NODE_ENV: !ENV '${NODE_ENV}'
- SPARKY_FITNESS_DB_HOST: !ENV '${SPARKY_FITNESS_DB_HOST}'
- SPARKY_FITNESS_DB_PORT: !ENV '${SPARKY_FITNESS_DB_PORT}'
- SPARKY_FITNESS_DB_NAME: !ENV '${SPARKY_FITNESS_DB_NAME}'
- SPARKY_FITNESS_DB_USER: !ENV '${SPARKY_FITNESS_DB_USER}'
- SPARKY_FITNESS_DB_PASSWORD: !ENV '${SPARKY_FITNESS_DB_PASSWORD}'
- SPARKY_FITNESS_APP_DB_USER: !ENV '${SPARKY_FITNESS_APP_DB_USER}'
- SPARKY_FITNESS_APP_DB_PASSWORD: !ENV '${SPARKY_FITNESS_APP_DB_PASSWORD}'
- SPARKY_FITNESS_API_ENCRYPTION_KEY: !ENV '${SPARKY_FITNESS_API_ENCRYPTION_KEY}'
- BETTER_AUTH_SECRET: !ENV '${BETTER_AUTH_SECRET}'
- SPARKY_FITNESS_FRONTEND_URL: !ENV '${SPARKY_FITNESS_FRONTEND_URL}'
- SPARKY_FITNESS_SERVER_HOST: !ENV '${SPARKY_FITNESS_SERVER_HOST}'
- SPARKY_FITNESS_SERVER_PORT: !ENV '${SPARKY_FITNESS_SERVER_PORT}'
- SPARKY_FITNESS_LOG_LEVEL: !ENV '${SPARKY_FITNESS_LOG_LEVEL}'
volumes:
- SPARKYFITNESS_UPLOADS_PATH: /uploads
- SPARKYFITNESS_BACKUPS_PATH: /backups
volumes:
SPARKYFITNESS_UPLOADS_PATH:
device: '/path/to/containers/sparkyfitness/uploads'
SPARKYFITNESS_BACKUPS_PATH:
device: '/path/to/containers/sparkyfitness/backups'
podman run -d --name sparkyfitness \
-e PUID=1000 \
-e PGID=1000 \
-e TZ=UTC \
-e NODE_ENV=production \
-e SPARKY_FITNESS_DB_HOST=127.0.0.1 \
-e SPARKY_FITNESS_DB_PORT=5433 \
-e SPARKY_FITNESS_DB_NAME=${SPARKY_FITNESS_DB_NAME} \
-e SPARKY_FITNESS_DB_USER=${SPARKY_FITNESS_DB_USER} \
-e SPARKY_FITNESS_DB_PASSWORD=${SPARKY_FITNESS_DB_PASSWORD} \
-e SPARKY_FITNESS_APP_DB_USER=${SPARKY_FITNESS_APP_DB_USER} \
-e SPARKY_FITNESS_APP_DB_PASSWORD=${SPARKY_FITNESS_APP_DB_PASSWORD} \
-e SPARKY_FITNESS_API_ENCRYPTION_KEY=${SPARKY_FITNESS_API_ENCRYPTION_KEY} \
-e BETTER_AUTH_SECRET=${BETTER_AUTH_SECRET} \
-e SPARKY_FITNESS_FRONTEND_URL=${SPARKY_FITNESS_FRONTEND_URL} \
-e SPARKY_FITNESS_SERVER_HOST=127.0.0.1 \
-e SPARKY_FITNESS_SERVER_PORT=3010 \
-e SPARKY_FITNESS_LOG_LEVEL=ERROR \
-v /path/to/containers/sparkyfitness/uploads:/uploads \
-v /path/to/containers/sparkyfitness/backups:/backups \
ghcr.io/daemonless/sparkyfitness:latest
- name: Deploy sparkyfitness
containers.podman.podman_container:
name: sparkyfitness
image: "ghcr.io/daemonless/sparkyfitness:latest"
state: started
restart_policy: always
env:
PUID: "1000"
PGID: "1000"
TZ: "UTC"
NODE_ENV: "production"
SPARKY_FITNESS_DB_HOST: "127.0.0.1"
SPARKY_FITNESS_DB_PORT: "5433"
SPARKY_FITNESS_DB_NAME: "${SPARKY_FITNESS_DB_NAME}"
SPARKY_FITNESS_DB_USER: "${SPARKY_FITNESS_DB_USER}"
SPARKY_FITNESS_DB_PASSWORD: "${SPARKY_FITNESS_DB_PASSWORD}"
SPARKY_FITNESS_APP_DB_USER: "${SPARKY_FITNESS_APP_DB_USER}"
SPARKY_FITNESS_APP_DB_PASSWORD: "${SPARKY_FITNESS_APP_DB_PASSWORD}"
SPARKY_FITNESS_API_ENCRYPTION_KEY: "${SPARKY_FITNESS_API_ENCRYPTION_KEY}"
BETTER_AUTH_SECRET: "${BETTER_AUTH_SECRET}"
SPARKY_FITNESS_FRONTEND_URL: "${SPARKY_FITNESS_FRONTEND_URL}"
SPARKY_FITNESS_SERVER_HOST: "127.0.0.1"
SPARKY_FITNESS_SERVER_PORT: "3010"
SPARKY_FITNESS_LOG_LEVEL: "ERROR"
volumes:
- "/path/to/containers/sparkyfitness/uploads:/uploads"
- "/path/to/containers/sparkyfitness/backups:/backups"
Interactive Configuration
Parameters
Environment Variables
| Variable | Default | Description |
|---|---|---|
PUID |
1000 |
User ID for the application process |
PGID |
1000 |
Group ID for the application process |
TZ |
${TZ} |
Timezone for the container |
NODE_ENV |
production |
Node runtime mode; leave as 'production' |
SPARKY_FITNESS_DB_HOST |
127.0.0.1 |
PostgreSQL host the backend connects to; leave as 127.0.0.1 (host networking) |
SPARKY_FITNESS_DB_PORT |
5433 |
PostgreSQL port; MUST match the sidecar's POSTGRES_PORT. Default 5433 (NOT 5432) so this can coexist with another host-networked Postgres (e.g. Immich on 5432) on the same host. ⚠ With network_mode: host, two Postgres on the same port silently collide — keep each service on a distinct port. NOTE: this only takes effect if the daemonless/postgres image honors POSTGRES_PORT (see project README / upstream fix); otherwise the sidecar falls back to 5432. |
SPARKY_FITNESS_DB_NAME |
${SPARKY_FITNESS_DB_NAME} |
PostgreSQL database name |
SPARKY_FITNESS_DB_USER |
${SPARKY_FITNESS_DB_USER} |
PostgreSQL superuser |
SPARKY_FITNESS_DB_PASSWORD |
${SPARKY_FITNESS_DB_PASSWORD} |
PostgreSQL password (from secrets.env) |
SPARKY_FITNESS_APP_DB_USER |
${SPARKY_FITNESS_APP_DB_USER} |
Limited app DB role the backend creates during migrations (from secrets) |
SPARKY_FITNESS_APP_DB_PASSWORD |
${SPARKY_FITNESS_APP_DB_PASSWORD} |
Password for the limited app DB role (from secrets) |
SPARKY_FITNESS_API_ENCRYPTION_KEY |
${SPARKY_FITNESS_API_ENCRYPTION_KEY} |
64-char hex encryption key (from secrets.env) |
BETTER_AUTH_SECRET |
${BETTER_AUTH_SECRET} |
Auth session signing secret (from secrets.env) |
SPARKY_FITNESS_FRONTEND_URL |
${SPARKY_FITNESS_FRONTEND_URL} |
Public URL of the frontend for CORS |
SPARKY_FITNESS_SERVER_HOST |
127.0.0.1 |
Internal bind address for the node backend; leave as 127.0.0.1 (nginx proxies to it) |
SPARKY_FITNESS_SERVER_PORT |
3010 |
Internal node backend port; leave as default |
SPARKY_FITNESS_LOG_LEVEL |
ERROR |
Backend log verbosity (e.g. ERROR, INFO, DEBUG) |
Volumes
| Path | Description |
|---|---|
/uploads |
User-uploaded files (profile pictures, exercise images) |
/backups |
Server backup data |
Implementation Details
- Architectures: amd64
- User:
bsd(UID/GID set via PUID/PGID). Defaults to1000:1000. - Base: Built on
ghcr.io/daemonless/base(FreeBSD 15.0).
Need help? Join our Discord community.