# Backend Environment Variables Example # Copy this file to .env and update with your values # Application NODE_ENV=production PORT=3001 # Security # Generate with: openssl rand -base64 32 JWT_SECRET=your-very-secure-jwt-secret-at-least-32-characters-long-example123456 # Admin 2FA (TOTP) secret encryption key — OPTIONAL. # Admin authenticator secrets are encrypted at rest (AES-256-GCM). By default # the key is derived from JWT_SECRET, so you do NOT need to set this. Set it # only if you want the MFA encryption key decoupled from JWT_SECRET (e.g. so # rotating JWT_SECRET doesn't invalidate enrolled authenticators). If you set # it, changing/losing it makes existing 2FA secrets undecryptable — recover # with: docker compose exec backend node scripts/reset-admin-mfa.js --all --yes # Generate with: openssl rand -base64 32 #MFA_ENCRYPTION_KEY= # Auth cookie Secure flag # unset - default: 'auto' in production, false in dev (#427) # true - always set Secure (HTTPS-only cookies; breaks plain-HTTP access — # login appears to succeed but the browser silently drops the # cookie, leaving you in a redirect loop. Only set this if you # ALWAYS reach the site via HTTPS) # false - never set Secure (allows HTTP; cookies not protected on HTTPS) # auto - decide per request: Secure on HTTPS, not on HTTP. Reads # req.secure from Express which respects X-Forwarded-Proto from a # trusted reverse proxy. This is the default and is the right # choice for most deployments. # # Why 'auto' is the default in production: # - On real HTTPS (reverse proxy with X-Forwarded-Proto), req.secure is # true → Secure flag is still emitted. No security regression vs. true. # - On plain HTTP (LAN access, first-time install before reverse proxy is # wired up), req.secure is false → Secure flag is omitted → login works # instead of silently looping back to /admin/login. # # When you'd set this explicitly: # - COOKIE_SECURE=true → strict HTTPS-only deployments where you want # defense in depth against accidentally serving over HTTP. # - COOKIE_SECURE=false → you intentionally only ever serve over HTTP and # don't want the per-request check (rare). # # Requirements for 'auto' mode to detect HTTPS correctly: # 1. Your reverse proxy MUST send X-Forwarded-Proto: https on HTTPS # requests. Standard configs for NPM/Traefik/Caddy do this by default. # 2. The proxy must be on a trusted IP range. By default PicPeak trusts # loopback and private networks (127.0.0.1, 10.x, 172.16-31.x, # 192.168.x, link-local). Proxies outside those ranges need custom # trust proxy configuration. # COOKIE_SECURE=auto # Cookie SameSite attribute (Lax | Strict | None). Default: Lax # COOKIE_SAMESITE=Lax # Cookie Domain — set this if serving auth cookies across subdomains. # Leave unset for same-origin setups. # COOKIE_DOMAIN=.example.com # URLs (adjust for your domain) ADMIN_URL=https://photos.example.com FRONTEND_URL=https://photos.example.com BACKEND_URL=https://photos.example.com # Or https://api.photos.example.com if separate # API URL for email assets (logos, images in emails) # This must be the publicly accessible URL where recipients can load images # If not set, defaults to http://localhost:3001 which will break images in production emails API_URL=https://photos.example.com/api # Database Configuration DATABASE_CLIENT=pg DB_HOST=localhost DB_PORT=5432 DB_USER=picpeak DB_PASSWORD=your-secure-database-password-change-this DB_NAME=picpeak # Email Configuration (Examples for common providers) # Gmail example: # SMTP_HOST=smtp.gmail.com # SMTP_PORT=587 # SMTP_SECURE=false # SMTP_USER=your-email@gmail.com # SMTP_PASS=your-app-specific-password # SendGrid example: SMTP_HOST=smtp.sendgrid.net SMTP_PORT=587 SMTP_SECURE=false SMTP_USER=apikey SMTP_PASS=your-sendgrid-api-key EMAIL_FROM=noreply@example.com # Storage Paths # IMPORTANT: STORAGE_PATH must be set to avoid file path resolution issues # Docker deployment: STORAGE_PATH=/app/storage EVENTS_PATH=/app/storage/events ARCHIVE_PATH=/app/storage/events/archived # Local development: # STORAGE_PATH=./storage # EVENTS_PATH=./storage/events # ARCHIVE_PATH=./storage/events/archived # File watcher (auto-import from the events/active folder, local storage only) # Max photos processed in parallel by the watcher. The boot scan and bulk # folder drops fire one handler per file — this bound keeps thumbnail # generation from exhausting memory on small hosts. Default: 2 # FILE_WATCHER_CONCURRENCY=2 # External-media folder watcher (issue 1187). Reference-mode events can opt in # per event (Event → Source Mode → "Watch folder for new files"); new images in # the folder are then imported without pressing Import. Deleted files are # never removed from the gallery. # EXTERNAL_MEDIA_WATCH=true # global kill switch # EXTERNAL_MEDIA_WATCH_POLLING=false # true = stat-polling instead of inotify (NFS/SMB mounts) # EXTERNAL_MEDIA_WATCH_POLL_INTERVAL_MS=5000 # EXTERNAL_MEDIA_WATCH_SWEEP_INTERVAL_MS=900000 # timer-driven pass over every watched event; 0 disables # EXTERNAL_MEDIA_WATCH_DEBOUNCE_MS=10000 # quiet period after the last change before the import runs # EXTERNAL_MEDIA_WATCH_STABILITY_MS=5000 # how long a file must stop growing before it counts as written # Analytics Backend Configuration (OPTIONAL) # Used for server-side tracking only # Primary configuration should be done through Admin UI > Settings > Analytics # UMAMI_URL=https://analytics.example.com # UMAMI_WEBSITE_ID=b4d3c2a1-5678-90ab-cdef-1234567890ab # Logging LOG_LEVEL=info # Optional product usage (#1110): disabled until explicit in-app consent. # USAGE_COLLECTOR_URL=https://usage.picpeak.app # Encryption material for the backend-only signing key (32+ characters). # Defaults to JWT_SECRET; keep it stable until participation has been deleted. # USAGE_ENCRYPTION_KEY=