# SourceBans++ production environment template (#1381 deliverable 4). # # Copy to `.env` next to `docker-compose.prod.yml`, fill in the # REQUIRED values, then run: # # docker compose -f docker-compose.prod.yml up -d # # `.env` is read automatically by `docker compose`. Do NOT commit it # to git — it carries the panel's JWT signing key and the DB password. # # All values below are EXAMPLES. Production values must be unique to # your deployment. # ============================================================================= # REQUIRED — the stack will refuse to start without these # ============================================================================= # JWT signing key. Persists across container recreations so admin # sessions survive redeploys. Generate once: # # openssl rand -base64 47 # # If you regenerate this value, every admin's session cookie becomes # invalid and they have to log in again. NEVER commit this value. SB_SECRET_KEY=replace-with-output-of-openssl-rand-base64-47 # Database root password (used by mariadb's init step for the first # boot of the `db` container only — the panel itself uses DB_USER/ # DB_PASS). Generate with `openssl rand -base64 24`. DB_ROOT_PASS=replace-with-strong-random-password # Database password for the panel user. Generate with # `openssl rand -base64 24`. Must NOT equal DB_ROOT_PASS. DB_PASS=replace-with-different-strong-random-password # ============================================================================= # RECOMMENDED — the panel runs without these, but features degrade # ============================================================================= # Steam Web API key. Required for SteamID name resolution and the # Steam OpenID login path. Get one from: # https://steamcommunity.com/dev/apikey # # The all-zero value below is the dev-stack placeholder; production # panels must use a real key. STEAMAPIKEY=00000000000000000000000000000000 # Default "From" address for outgoing panel email (password resets, # protest notifications, etc.). Defaults to noreply@$(hostname) inside # the container if unset. SB_EMAIL=noreply@yourdomain.example # ============================================================================= # FIRST-BOOT INSTALL — initial admin account seed # ============================================================================= # # When the entrypoint runs against an empty DB on first boot, it seeds # an Owner-flagged admin from these four values. Set ALL four — the # entrypoint skips the seed (and logs a warning) if any is missing, # leaving you unable to log in until you create an admin by hand. # # After the first successful boot, these values are no-ops (the # entrypoint only runs the seed when the DB is empty). # Display name shown in the panel sidebar. INITIAL_ADMIN_NAME=Admin # SteamID2 of the initial admin. Looks like `STEAM_0:1:12345678`. # Required because the panel's primary identity is the SteamID, not # the display name. Convert SteamID64 → SteamID2 with # https://steamid.io or https://steamidfinder.com. INITIAL_ADMIN_STEAM=STEAM_0:0:0 # Email address used for password-reset flows. INITIAL_ADMIN_EMAIL=admin@yourdomain.example # Login password. Minimum 8 characters per the panel's bcrypt floor. # Change this immediately after first login via the "Your account" # page; the env var is the SEED, not a persistent source of truth. INITIAL_ADMIN_PASSWORD=replace-with-strong-temporary-password # ============================================================================= # OPTIONAL — knobs most operators leave at defaults # ============================================================================= # Host port the panel binds to. Default 8080 to avoid colliding with # anything already on port 80 (e.g. a host nginx). Behind a reverse # proxy, you'd typically point the proxy at this port. SBPP_HOST_PORT=8080 # Host bind. Empty = bind to all interfaces (the panel is reachable # from the public internet on SBPP_HOST_PORT). For "reverse-proxy # only" deployments, set to `127.0.0.1:` (note the trailing colon) # so only the proxy can talk to the panel. SBPP_BIND= # Image tag to pull from GHCR. Defaults to `latest` (newest # released stable). Pin to a specific version (e.g. `1.7.0`) for # reproducible deployments; pin to `main` to ride the bleeding # edge (NOT recommended for production). SBPP_IMAGE_TAG=latest # Trusted reverse-proxy CIDR ranges. Space-separated. When set, # Apache trusts X-Forwarded-For / X-Forwarded-Proto from these # addresses ONLY. Empty default = no proxy trust (so a misconfigured # deployment doesn't accidentally trust arbitrary clients). # # For Caddy / Traefik / nginx on the same compose network, the # default Docker bridge ranges cover everything: # # SBPP_TRUSTED_PROXIES=172.16.0.0/12 10.0.0.0/8 # # For Cloudflare's network in front of the panel: # # SBPP_TRUSTED_PROXIES=173.245.48.0/20 103.21.244.0/22 ... # (see https://www.cloudflare.com/ips/ for the current list) SBPP_TRUSTED_PROXIES= # Database name / user / port / prefix / charset. Defaults match # the dev stack; only change if pointing at an existing DB. DB_NAME=sourcebans DB_USER=sourcebans DB_HOST=db DB_PORT=3306 DB_PREFIX=sb DB_CHARSET=utf8mb4 # DATABASE_URL — alternative to the split DB_* vars above. When set, # the entrypoint parses the URL into DB_HOST/DB_PORT/DB_USER/DB_PASS/ # DB_NAME and that value wins over the split vars. Useful when the # DB is provisioned by an app platform (Render / Fly / Railway) # that emits a single connection URL. # # DATABASE_URL=mysql://user:pass@host:port/dbname?charset=utf8mb4 DATABASE_URL= # SBPP_AUTO_INSTALL — when "1" (default), the entrypoint runs the # first-boot install + migrations on every container start (idempotent # after the first run). Set to "0" if you provisioned the DB by hand # (managed RDS, restored backup, etc.) and want the entrypoint to # only run migrations. SBPP_AUTO_INSTALL=1 # PORT (advanced — do NOT set on a docker-compose deploy) # # Inside the container, the entrypoint rewrites Apache's `Listen 80` # to `Listen $PORT` when this env var is set. The mechanism exists # so the image runs unmodified on app-platform deploys # (Render / Fly / Railway / Heroku) that inject `PORT` and expect # the app to bind to it. # # On a docker-compose deploy (this file), `docker-compose.prod.yml` # hardcodes `${SBPP_HOST_PORT}:80` — host port → container port 80. # Setting `PORT=8000` here would rewrite the container's listen to # 8000 BUT compose would still map the host port to the container's # port 80 (which nothing is listening on), making the panel # unreachable. SBPP_HOST_PORT above is the right knob for the # compose host-side; leave PORT unset on compose. # # LOW-3 of the #1381 review documented this trap. # PORT=8080 # SBPP_CONFIG_PATH — where the panel reads/writes config.php. Default # (empty) = the image's writable layer at # /var/www/html/web/config.php. Set this to a path on a volume or a # Docker-secret mount when you want config.php to survive container # image rebuilds (the alternative is setting SB_SECRET_KEY explicitly # in this file, which the entrypoint will then bake into config.php). SBPP_CONFIG_PATH= # ============================================================================= # DOCKER SECRETS / *_FILE PATTERN (advanced) # ============================================================================= # # Every secret-shaped env var above (SB_SECRET_KEY, DB_PASS, DB_ROOT_PASS, # STEAMAPIKEY, INITIAL_ADMIN_PASSWORD) accepts a sibling `_FILE` form # that points at a file path inside the container. When the sibling is # set AND the file exists, the entrypoint reads the file's contents # and uses them as the secret value, overriding the plain env var. # # This is the standard Docker Swarm / Kubernetes secret-injection # idiom. Example: # # # docker-compose.prod.override.yml # services: # web: # environment: # SB_SECRET_KEY_FILE: /run/secrets/sbpp_jwt_key # DB_PASS_FILE: /run/secrets/sbpp_db_pass # secrets: # - sbpp_jwt_key # - sbpp_db_pass # secrets: # sbpp_jwt_key: # file: ./secrets/jwt_key # sbpp_db_pass: # file: ./secrets/db_pass # # Plain `docker compose` deploys typically use the env-var form above # (simpler); the `_FILE` form is for Swarm / k8s where secrets are # managed by the orchestrator. # ============================================================================= # REVERSE PROXY (only needed if you uncomment the caddy: service) # ============================================================================= # When you uncomment the `caddy:` block in docker-compose.prod.yml, # set your panel's public domain here. Caddy automatically provisions # Let's Encrypt TLS for the value. SBPP_DOMAIN=panel.yourdomain.example