Skip to content

SparkyFitness SparkyFitness

Description / nameInput element
Container Registry
Container Configuration Root Path
Timezone
User ID
Group ID
SparkyFitness /app/server/uploads Path
SparkyFitness /app/server/backup Path

Build Status Last Commit OCI Pulls

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 — recommended.

Parts

Service Image Role
sparkyfitness ghcr.io/daemonless/sparkyfitness:${SPARKYFITNESS_TAG:-latest} docs
sparkyfitness-db ghcr.io/daemonless/postgres:18 docs

Before deploying, ensure your host environment is ready. See the Quick Start Guide for host setup instructions, including the security model for host vs. container privileges.

Deployment

1. Save as .env and fill in what is empty:

# SparkyFitness for FreeBSD (Daemonless)
# Copy to .env and edit.

# Timezone (TZ identifier)
TZ=UTC

# Where data lives on the host
UPLOADS_LOCATION=/containers/sparkyfitness/uploads
BACKUPS_LOCATION=/containers/sparkyfitness/backups
DB_DATA_LOCATION=/containers/sparkyfitness/postgres

# PostgreSQL superuser + database (provisioned by the sidecar)
SPARKY_FITNESS_DB_NAME=sparkyfitness
SPARKY_FITNESS_DB_USER=sparky
# Change this to a random password! A-Za-z0-9 only.
SPARKY_FITNESS_DB_PASSWORD=sparky

# Limited app role the backend creates during migrations
SPARKY_FITNESS_APP_DB_USER=sparky_app
SPARKY_FITNESS_APP_DB_PASSWORD=sparky_app

# Secrets: no defaults on purpose -- these become the catalog's install
# defaults, and a shipped value would be shared by every install. Generate
# your own, e.g. `openssl rand -hex 32` for each.
SPARKY_FITNESS_API_ENCRYPTION_KEY=
BETTER_AUTH_SECRET=

# Public URL the browser uses to reach the app, e.g. http://myhost:3004.
# No default on purpose: Better Auth compares this against the request origin
# and rejects the sign-up POST when it does not match, so a shipped default is
# wrong for everyone who does not browse from the server itself.
SPARKY_FITNESS_FRONTEND_URL=

2. Save as compose.yaml:

name: sparkyfitness

services:
  sparkyfitness:
    # Tag in a ${VAR} so the catalog exposes it as an image_tag variable and the
    # install wizard can offer this repo's published versions. The database is
    # pinned deliberately: retagging it would be a destructive migration.
    image: ghcr.io/daemonless/sparkyfitness:${SPARKYFITNESS_TAG:-latest}
    container_name: sparkyfitness
    restart: unless-stopped
    network_mode: host
    depends_on:
      - sparkyfitness-db

    environment:
      - PUID=1000
      - PGID=1000
      - TZ=${TZ:-UTC}
      - NODE_ENV=production
      - SPARKY_FITNESS_DB_HOST=${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:
      - ${UPLOADS_LOCATION}:/app/server/uploads
      - ${BACKUPS_LOCATION}:/app/server/backup

  sparkyfitness-db:
    image: ghcr.io/daemonless/postgres:18
    container_name: sparkyfitness-db
    restart: unless-stopped
    network_mode: host

    annotations:
      org.freebsd.jail.allow.sysvipc: "true"

    # Generic daemonless PostgreSQL 18 image. Only the superuser role + database
    # are provisioned here; the SparkyFitness backend creates its own limited
    # app role (SPARKY_FITNESS_APP_DB_USER) during migrations.
    environment:
      - POSTGRES_DB=${SPARKY_FITNESS_DB_NAME}
      - POSTGRES_USER=${SPARKY_FITNESS_DB_USER}
      - POSTGRES_PASSWORD=${SPARKY_FITNESS_DB_PASSWORD}
      # Distinct port (NOT 5432) so this can run alongside another host-networked Postgres
      # (e.g. Immich on 5432) without colliding. ⚠ Requires the daemonless/postgres image to
      # honor POSTGRES_PORT — if it doesn't, it falls back to 5432 and WILL collide. Two
      # host-net Postgres must never share a port.
      - POSTGRES_PORT=5433

    volumes:
      - ${DB_DATA_LOCATION}:/var/lib/postgresql/data

Then run podman-compose up -d.

# .env

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=sparkyfitness
SPARKY_FITNESS_DB_USER=sparky
SPARKY_FITNESS_DB_PASSWORD=sparky
SPARKY_FITNESS_APP_DB_USER=sparky_app
SPARKY_FITNESS_APP_DB_PASSWORD=sparky_app
SPARKY_FITNESS_API_ENCRYPTION_KEY=<SPARKY_FITNESS_API_ENCRYPTION_KEY>
BETTER_AUTH_SECRET=<BETTER_AUTH_SECRET>
SPARKY_FITNESS_FRONTEND_URL=
SPARKY_FITNESS_SERVER_HOST=127.0.0.1
SPARKY_FITNESS_SERVER_PORT=3010
SPARKY_FITNESS_LOG_LEVEL=ERROR
UPLOADS_LOCATION=/containers/sparkyfitness/uploads
BACKUPS_LOCATION=/containers/sparkyfitness/backups
DB_DATA_LOCATION=/containers/sparkyfitness/postgres
# appjail-director.yml

options:
  - alias:
  - ip4_inherit:
services:
  sparkyfitness:
    name: sparkyfitness
    options:
      - container: '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}'
        - UPLOADS_LOCATION: !ENV '${UPLOADS_LOCATION}'
        - BACKUPS_LOCATION: !ENV '${BACKUPS_LOCATION}'
        - DB_DATA_LOCATION: !ENV '${DB_DATA_LOCATION}'
    volumes:
      - SPARKYFITNESS_UPLOADS_PATH: /app/server/uploads
      - SPARKYFITNESS_BACKUP_PATH: /app/server/backup
  sparkyfitness-db:
    name: sparkyfitness_db
    options:
      - from: ghcr.io/daemonless/postgres:18
      - template: !ENV '${PWD}/postgres-template.conf'
    oci:
      environment:
        - POSTGRES_DB: !ENV '${SPARKY_FITNESS_DB_NAME}'
        - POSTGRES_USER: !ENV '${SPARKY_FITNESS_DB_USER}'
        - POSTGRES_PASSWORD: !ENV '${SPARKY_FITNESS_DB_PASSWORD}'
        - POSTGRES_PORT: "5433"
    volumes:
      - db_data: /var/lib/postgresql/data
volumes:
  SPARKYFITNESS_UPLOADS_PATH:
    device: '/containers/sparkyfitness/uploads'
  SPARKYFITNESS_BACKUP_PATH:
    device: '/containers/sparkyfitness/backup'
  db_data:
    device: !ENV '${DB_DATA_LOCATION}'
1
2
3
4
5
6
7
# Makejail

ARG tag=latest

OPTION container=boot
OPTION overwrite=force
OPTION from=ghcr.io/daemonless/sparkyfitness:${tag}

Save the files above, then run appjail-director up.

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 UTC Timezone for the container
NODE_ENV production Node runtime mode; leave as 'production'
SPARKY_FITNESS_DB_HOST 127.0.0.1 Where the backend finds PostgreSQL (fjord sets this; 127.0.0.1 on 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 sparkyfitness PostgreSQL database name
SPARKY_FITNESS_DB_USER sparky PostgreSQL superuser
SPARKY_FITNESS_DB_PASSWORD sparky PostgreSQL password
SPARKY_FITNESS_APP_DB_USER sparky_app Limited app DB role the backend creates during migrations
SPARKY_FITNESS_APP_DB_PASSWORD sparky_app Password for the limited app DB role
SPARKY_FITNESS_API_ENCRYPTION_KEY <SPARKY_FITNESS_API_ENCRYPTION_KEY> 64-char hex encryption key
BETTER_AUTH_SECRET <BETTER_AUTH_SECRET> Auth session signing secret
SPARKY_FITNESS_FRONTEND_URL `` URL you will open this app at, e.g. http://myhost:3004 (sign-up fails if it does not match)
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)
UPLOADS_LOCATION /containers/sparkyfitness/uploads Path to store user uploads (profile pictures, exercise images)
BACKUPS_LOCATION /containers/sparkyfitness/backups Path to store server backups
DB_DATA_LOCATION /containers/sparkyfitness/postgres Path to store PostgreSQL data files

Volumes

Path Description
/app/server/uploads User-uploaded files (profile pictures, exercise images)
/app/server/backup 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.1).

Need help? Join our Discord community.