Skip to content

SparkyFitness

Description / nameInput element
Container Registry
Container Configuration Root Path
Timezone
User ID
Group ID
SparkyFitness /uploads Path
SparkyFitness /backups Path

Build Status Last Commit

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'
ARG tag=latest

OPTION overwrite=force
OPTION from=ghcr.io/daemonless/sparkyfitness:${tag}
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 to 1000:1000.
  • Base: Built on ghcr.io/daemonless/base (FreeBSD 15.0).

Need help? Join our Discord community.