services: sparkyfitness-db: image: postgres:18.3-alpine container_name: sparkyfitness-db restart: always # Uncomment below to expose PostgreSQL to the host (e.g., for pgAdmin, DBeaver). # ports: # - "${SPARKY_FITNESS_DB_PORT:-5432}:5432" environment: # These two are only read the first time the database initialises, so the # defaults below are what an install that never set them is already using. # Do not change them on an existing instance: PostgreSQL ignores the new # value and the server then authenticates with credentials that no longer # match. See docs/src/install/environment-variables.md. POSTGRES_DB: ${SPARKY_FITNESS_DB_NAME:-sparkyfitness_db} POSTGRES_USER: ${SPARKY_FITNESS_DB_USER:-sparky} POSTGRES_PASSWORD: ${SPARKY_FITNESS_DB_PASSWORD:-} # Uncomment the line below and comment the line above to use a file-based secret # POSTGRES_PASSWORD_FILE: /run/secrets/sparkyfitness_db_password PUID: 1000 GUID: 1000 volumes: - ${DB_PATH:-./postgresql}:/var/lib/postgresql # The server applies migrations at startup, so it must not race Postgres # while it is still initialising. healthcheck: test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"] interval: 10s timeout: 5s retries: 10 start_period: 30s # Uncomment the below section to mount the file-based secrets #secrets: # - sparkyfitness_db_password networks: - sparkyfitness-network # Use the new named network sparkyfitness-server: image: codewithcj/sparkyfitness_server:latest # Use pre-built image environment: # --- Database Connection --- SPARKY_FITNESS_DB_HOST: ${SPARKY_FITNESS_DB_HOST:-sparkyfitness-db} # Use the service name 'sparkyfitness-db' for inter-container communication SPARKY_FITNESS_DB_PORT: 5432 # Uses internal container port. Do NOT change this if SPARKY_FITNESS_DB_HOST is using container name. SPARKY_FITNESS_DB_NAME: ${SPARKY_FITNESS_DB_NAME:-sparkyfitness_db} SPARKY_FITNESS_DB_USER: ${SPARKY_FITNESS_DB_USER:-sparky} SPARKY_FITNESS_DB_PASSWORD: ${SPARKY_FITNESS_DB_PASSWORD:-} # Uncomment the line below and comment the line above to use a file-based secret # SPARKY_FITNESS_DB_PASSWORD_FILE: /run/secrets/sparkyfitness_db_password SPARKY_FITNESS_APP_DB_USER: ${SPARKY_FITNESS_APP_DB_USER:-sparky_app} SPARKY_FITNESS_APP_DB_PASSWORD: ${SPARKY_FITNESS_APP_DB_PASSWORD:-} # Uncomment the line below and comment the line above to use a file-based secret # SPARKY_FITNESS_APP_DB_PASSWORD_FILE: /run/secrets/sparkyfitness_app_db_password # --- Essential App Security --- SPARKY_FITNESS_FRONTEND_URL: ${SPARKY_FITNESS_FRONTEND_URL:-http://0.0.0.0:3004} SPARKY_FITNESS_API_ENCRYPTION_KEY: ${SPARKY_FITNESS_API_ENCRYPTION_KEY:-} # Uncomment the line below and comment the line above to use a file-based secret # SPARKY_FITNESS_API_ENCRYPTION_KEY_FILE: /run/secrets/sparkyfitness_api_key BETTER_AUTH_SECRET: ${BETTER_AUTH_SECRET:-} # Uncomment the line below and comment the line above to use a file-based secret # BETTER_AUTH_SECRET_FILE: /run/secrets/sparkyfitness_better_auth_secret # --- Server Runtime, Admin & Demo Mode --- NODE_ENV: ${NODE_ENV:-production} TZ: ${TZ:-Etc/UTC} SPARKY_FITNESS_LOG_LEVEL: ${SPARKY_FITNESS_LOG_LEVEL:-ERROR} SPARKY_FITNESS_SERVER_PORT: ${SPARKY_FITNESS_SERVER_PORT:-3010} # Port the backend listens on inside its container SPARKY_FITNESS_DISABLE_SIGNUP: ${SPARKY_FITNESS_DISABLE_SIGNUP:-false} SPARKY_FITNESS_ADMIN_EMAIL: ${SPARKY_FITNESS_ADMIN_EMAIL:-} # User with this email can access the admin panel SPARKY_FITNESS_DEMO_MODE: ${SPARKY_FITNESS_DEMO_MODE:-false} SPARKY_FITNESS_DEMO_EMAIL: ${SPARKY_FITNESS_DEMO_EMAIL:-} SPARKY_FITNESS_DEMO_PASSWORD: ${SPARKY_FITNESS_DEMO_PASSWORD:-} # --- Admin policy toggles (optional ENV override) --- # Also settable in the Admin UI, which stores them in global_settings. # Setting any of these to 'true' here forces it on regardless of the # stored value; leaving them empty defers to the Admin UI. SPARKY_FITNESS_PUBLIC_API_DOCS: ${SPARKY_FITNESS_PUBLIC_API_DOCS:-} DEV_TOOLS_ENABLED: ${DEV_TOOLS_ENABLED:-} ALLOW_PRIVATE_NETWORK_AI: ${ALLOW_PRIVATE_NETWORK_AI:-} ALLOW_PRIVATE_NETWORK_FOOD_PROVIDERS: ${ALLOW_PRIVATE_NETWORK_FOOD_PROVIDERS:-} # --- Network, Reverse Proxy & CORS --- ALLOW_PRIVATE_NETWORK_CORS: ${ALLOW_PRIVATE_NETWORK_CORS:-false} SPARKY_FITNESS_EXTRA_TRUSTED_ORIGINS: ${SPARKY_FITNESS_EXTRA_TRUSTED_ORIGINS:-} SPARKY_FITNESS_REAL_IP_HEADER: ${SPARKY_FITNESS_REAL_IP_HEADER:-} SPARKY_FITNESS_TRUSTED_PROXY_HOPS: ${SPARKY_FITNESS_TRUSTED_PROXY_HOPS:-} # --- Email Settings (optional) --- SPARKY_FITNESS_EMAIL_HOST: ${SPARKY_FITNESS_EMAIL_HOST:-} SPARKY_FITNESS_EMAIL_PORT: ${SPARKY_FITNESS_EMAIL_PORT:-} SPARKY_FITNESS_EMAIL_SECURE: ${SPARKY_FITNESS_EMAIL_SECURE:-} SPARKY_FITNESS_EMAIL_USER: ${SPARKY_FITNESS_EMAIL_USER:-} SPARKY_FITNESS_EMAIL_PASS: ${SPARKY_FITNESS_EMAIL_PASS:-} # Uncomment the line below and comment the line above to use a file-based secret # SPARKY_FITNESS_EMAIL_PASS_FILE: /run/secrets/sparkyfitness_email_pass SPARKY_FITNESS_EMAIL_FROM: ${SPARKY_FITNESS_EMAIL_FROM:-} # --- Login & OIDC Authentication (optional) --- SPARKY_FITNESS_FORCE_EMAIL_LOGIN: ${SPARKY_FITNESS_FORCE_EMAIL_LOGIN:-} SPARKY_FITNESS_DISABLE_EMAIL_LOGIN: ${SPARKY_FITNESS_DISABLE_EMAIL_LOGIN:-} SPARKY_FITNESS_OIDC_AUTH_ENABLED: ${SPARKY_FITNESS_OIDC_AUTH_ENABLED:-} SPARKY_FITNESS_OIDC_PROVIDER_SLUG: ${SPARKY_FITNESS_OIDC_PROVIDER_SLUG:-} SPARKY_FITNESS_OIDC_PROVIDER_NAME: ${SPARKY_FITNESS_OIDC_PROVIDER_NAME:-} SPARKY_FITNESS_OIDC_AUTO_REGISTER: ${SPARKY_FITNESS_OIDC_AUTO_REGISTER:-} SPARKY_FITNESS_OIDC_LOGO_URL: ${SPARKY_FITNESS_OIDC_LOGO_URL:-} SPARKY_FITNESS_OIDC_ISSUER_URL: ${SPARKY_FITNESS_OIDC_ISSUER_URL:-} SPARKY_FITNESS_OIDC_DOMAIN: ${SPARKY_FITNESS_OIDC_DOMAIN:-} SPARKY_FITNESS_OIDC_CLIENT_ID: ${SPARKY_FITNESS_OIDC_CLIENT_ID:-} # Uncomment the line below and comment the line above to use a file-based secret # SPARKY_FITNESS_OIDC_CLIENT_ID_FILE: /run/secrets/sparkyfitness_oidc_client_id SPARKY_FITNESS_OIDC_CLIENT_SECRET: ${SPARKY_FITNESS_OIDC_CLIENT_SECRET:-} # Uncomment the line below and comment the line above to use a file-based secret # SPARKY_FITNESS_OIDC_CLIENT_SECRET_FILE: /run/secrets/sparkyfitness_oidc_client_secret SPARKY_FITNESS_OIDC_SCOPE: ${SPARKY_FITNESS_OIDC_SCOPE:-} SPARKY_FITNESS_OIDC_AUTO_REDIRECT: ${SPARKY_FITNESS_OIDC_AUTO_REDIRECT:-} SPARKY_FITNESS_OIDC_ADMIN_GROUP: ${SPARKY_FITNESS_OIDC_ADMIN_GROUP:-} SPARKY_FITNESS_OIDC_TOKEN_AUTH_METHOD: ${SPARKY_FITNESS_OIDC_TOKEN_AUTH_METHOD:-} SPARKY_FITNESS_OIDC_ID_TOKEN_SIGNED_ALG: ${SPARKY_FITNESS_OIDC_ID_TOKEN_SIGNED_ALG:-} SPARKY_FITNESS_OIDC_USERINFO_SIGNED_ALG: ${SPARKY_FITNESS_OIDC_USERINFO_SIGNED_ALG:-} SPARKY_FITNESS_OIDC_TIMEOUT: ${SPARKY_FITNESS_OIDC_TIMEOUT:-} # --- Integrations --- GARMIN_MICROSERVICE_URL: ${GARMIN_MICROSERVICE_URL:-http://sparkyfitness-garmin:${GARMIN_SERVICE_PORT:-8000}} # Garmin microservice URL # --- Outbound Proxy Settings (optional) --- HTTP_PROXY: ${HTTP_PROXY:-} # Optional forward proxy for the server's outbound requests HTTPS_PROXY: ${HTTPS_PROXY:-} NO_PROXY: ${NO_PROXY:-} # --- Storage Paths (optional, standalone installs) --- SPARKY_FITNESS_CUSTOM_UPLOADS_DIRECTORY: ${SPARKY_FITNESS_CUSTOM_UPLOADS_DIRECTORY:-} SPARKY_FITNESS_CUSTOM_BACKUP_DIRECTORY: ${SPARKY_FITNESS_CUSTOM_BACKUP_DIRECTORY:-} # --- Rate Limiting (optional) --- SPARKY_FITNESS_SIGN_IN_RATELIMIT_MAX: ${SPARKY_FITNESS_SIGN_IN_RATELIMIT_MAX:-} SPARKY_FITNESS_SIGN_IN_RATELIMIT_WINDOW: ${SPARKY_FITNESS_SIGN_IN_RATELIMIT_WINDOW:-} SPARKY_FITNESS_API_KEY_RATELIMIT_WINDOW_MS: ${SPARKY_FITNESS_API_KEY_RATELIMIT_WINDOW_MS:-} SPARKY_FITNESS_API_KEY_RATELIMIT_MAX_REQUESTS: ${SPARKY_FITNESS_API_KEY_RATELIMIT_MAX_REQUESTS:-} PUID: 1000 GUID: 1000 networks: - sparkyfitness-network # Use the new named network restart: always depends_on: sparkyfitness-db: # Wait for Postgres to accept connections, not just for the container to # exist -- startup migrations run before the server listens. condition: service_healthy volumes: - ${SERVER_BACKUP_PATH:-./backup}:/app/SparkyFitnessServer/backup # Mount volume for backups - ${SERVER_UPLOADS_PATH:-./uploads}:/app/SparkyFitnessServer/uploads # Mount volume for Profile pictures and excercise images # Uncomment the below section to mount the file-based secrets #secrets: # - sparkyfitness_db_password # - sparkyfitness_app_db_password # - sparkyfitness_api_key # - sparkyfitness_better_auth_secret # - sparkyfitness_email_pass # - sparkyfitness_oidc_client_id # - sparkyfitness_oidc_client_secret sparkyfitness-frontend: image: codewithcj/sparkyfitness:latest # Use pre-built image ports: # Container port must track NGINX_LISTEN_PORT, or nginx listens on an # unpublished port and the app becomes unreachable. - "${SPARKY_FITNESS_FRONTEND_PORT:-3004}:${NGINX_LISTEN_PORT:-80}" environment: SPARKY_FITNESS_FRONTEND_URL: ${SPARKY_FITNESS_FRONTEND_URL} # Where the frontend's nginx proxies /api, /health-data and /uploads. # This is resolved inside the frontend container, on the compose network, # so it must name the backend service — "localhost" here points at the # frontend itself and every API call returns 502. Only override it to # reach a backend outside this compose project. The port must match the # one passed to the server service above. SPARKY_FITNESS_SERVER_HOST: ${SPARKY_FITNESS_SERVER_HOST:-sparkyfitness-server} SPARKY_FITNESS_SERVER_PORT: ${SPARKY_FITNESS_SERVER_PORT:-3010} NGINX_RATE_LIMIT: ${NGINX_RATE_LIMIT:-5r/s} NGINX_RESOLVER: ${NGINX_RESOLVER:-} NGINX_LISTEN_PORT: ${NGINX_LISTEN_PORT:-} NGINX_ACCESS_LOG: ${NGINX_ACCESS_LOG:-} NGINX_ERROR_LOG: ${NGINX_ERROR_LOG:-} NGINX_DUMP_CONFIG: ${NGINX_DUMP_CONFIG:-false} PUID: 1000 GUID: 1000 networks: - sparkyfitness-network # Use the new named network restart: always depends_on: - sparkyfitness-server # Frontend depends on the server #- sparkyfitness-garmin # Frontend depends on Garmin microservice. Enable if you are using Garmin Connect features. # Garmin integration is still work in progress. Enable once table is ready. # sparkyfitness-garmin: # image: codewithcj/sparkyfitness_garmin:latest # container_name: sparkyfitness-garmin # environment: # GARMIN_MICROSERVICE_URL: http://sparkyfitness-garmin:${GARMIN_SERVICE_PORT} # GARMIN_SERVICE_PORT: ${GARMIN_SERVICE_PORT} # GARMIN_SERVICE_IS_CN: ${GARMIN_SERVICE_IS_CN} # set to true for China region. Everything else should be false. Optional - defaults to false # PUID: 1000 # GUID: 1000 # networks: # - sparkyfitness-network # restart: unless-stopped # depends_on: # - sparkyfitness-db # - sparkyfitness-server networks: sparkyfitness-network: driver: bridge # Uncomment the below section to mount the file-based secrets # These are the file paths on the host and should not be the same as the paths mentioned above which are in the containers themselves #secrets: # sparkyfitness_db_password: # file: ${SPARKY_FITNESS_DB_PASSWORD_FILE:-./sparkyfitness_db_password.key} # sparkyfitness_app_db_password: # file: ${SPARKY_FITNESS_APP_DB_PASSWORD_FILE:-./sparkyfitness_app_db_password.key} # sparkyfitness_api_key: # file: ${SPARKY_FITNESS_API_ENCRYPTION_KEY_FILE:-./sparkyfitness_api_key.key} # sparkyfitness_better_auth_secret: # file: ${BETTER_AUTH_SECRET_FILE:-./sparkyfitness_better_auth_secret.key} # sparkyfitness_email_pass: # file: ${SPARKY_FITNESS_EMAIL_PASS_FILE:-./sparkyfitness_email_pass.key} # sparkyfitness_oidc_client_id: # file: ${SPARKY_FITNESS_OIDC_CLIENT_ID_FILE:-./sparkyfitness_oidc_client_id.key} # sparkyfitness_oidc_client_secret: # file: ${SPARKY_FITNESS_OIDC_CLIENT_SECRET_FILE:-./sparkyfitness_oidc_client_secret.key}