Environment Variables
Complete reference for all PicPeak environment variables. Set these in the .env file at the project root.
Avoid $ characters in passwords. Docker Compose interprets $ as variable substitution. Either omit $ entirely or escape it as $$.
Core
| Variable | Required | Default | Description |
|---|---|---|---|
NODE_ENV | No | production | Environment mode |
JWT_SECRET | Yes | --- | Authentication secret. Generate with openssl rand -base64 64 |
TZ | No | UTC | Timezone |
SHUTDOWN_TIMEOUT_MS | No | 8000 | Backend shutdown deadline in milliseconds after SIGTERM or SIGINT; forces an exit when exceeded. Set in the backend container’s environment and keep below Docker Compose’s stop_grace_period. See Stopping and restarting. |
TRUST_PROXY | No | loopback, linklocal, uniquelocal | Which upstream proxies PicPeak trusts for X-Forwarded-For / X-Forwarded-Proto. Accepts any Express trust proxy value: a number of hops, loopback, linklocal, uniquelocal, a CIDR, a comma list, or true. The default covers Docker networks and private-network proxies. A proxy with a public address needs an explicit entry, otherwise per-IP features such as the API rate limiter see the proxy’s address for every visitor. |
Authentication cookies
Admin, gallery, and guest sessions are carried in HTTP cookies. These variables control how those cookies are set.
| Variable | Required | Default | Description |
|---|---|---|---|
COOKIE_SECURE | No | follows NODE_ENV (production → true) | true, false, or auto — see below |
COOKIE_SAMESITE | No | Lax | Lax, Strict, or None |
COOKIE_DOMAIN | No | --- | Set to .example.com only if serving cookies across subdomains |
COOKIE_SECURE values
| Value | Behavior | When to use |
|---|---|---|
| (unset) | Follows NODE_ENV — true in production, false in development | Default. Fine for typical HTTPS-only deployments |
true | Always set the Secure flag (HTTPS-only cookies) | Explicit HTTPS-only deployments |
false | Never set the Secure flag | Local/LAN-only deployments with no HTTPS |
auto | Decide per request based on the actual protocol | Mixed HTTPS + LAN HTTP access (see below) |
Mixed HTTPS/HTTP deployments (COOKIE_SECURE=auto)
A common self-hosted setup:
- Public: HTTPS via a reverse proxy like Nginx Proxy Manager, Traefik, or Caddy (e.g.
https://photos.example.com) - LAN: Plain HTTP on a private address (e.g.
http://192.168.1.50:3001) for admin access without going through the proxy
With a static COOKIE_SECURE=true, LAN login breaks because browsers refuse to send Secure cookies over HTTP. With COOKIE_SECURE=false, HTTPS login loses the security of the Secure flag. The auto value fixes this by deciding the Secure flag per request — the backend reads req.secure from Express, which reflects the X-Forwarded-Proto header forwarded by your reverse proxy.
Requirements for auto mode:
- Your reverse proxy must forward
X-Forwarded-Proto: httpson HTTPS requests. Nginx Proxy Manager, Traefik, and Caddy do this by default. Custom Nginx configs needproxy_set_header X-Forwarded-Proto $scheme;. - Your proxy must be on a trusted IP range. PicPeak trusts
loopback, linklocal, uniquelocal(127.0.0.1, 10.x, 172.16–31.x, 192.168.x, link-local, ULA). This covers Docker networks and most self-hosted setups. Proxies outside those ranges won’t have theirX-Forwarded-Protohonored.
Example .env entry:
COOKIE_SECURE=autoauto is opt-in. The default behavior (following NODE_ENV) is unchanged — existing deployments see no difference unless they explicitly set COOKIE_SECURE=auto.
Database
| Variable | Required | Default | Description |
|---|---|---|---|
DATABASE_CLIENT | No | pg | Database client (pg or sqlite3) |
DB_HOST | No | postgres | PostgreSQL host (defaults to bundled container) |
DB_PORT | No | 5432 | PostgreSQL port |
DB_USER | No | picpeak | Database username |
DB_PASSWORD | Yes | --- | Database password |
DB_NAME | No | picpeak_prod | Database name |
Redis
| Variable | Required | Default | Description |
|---|---|---|---|
REDIS_PASSWORD | Yes | --- | Redis password |
Admin account
| Variable | Required | Default | Description |
|---|---|---|---|
ADMIN_USERNAME | No | admin | Initial admin username |
ADMIN_EMAIL | No | [email protected] | Initial admin email (used for login) |
ADMIN_PASSWORD | No | auto-generated | Initial admin password |
Email (SMTP)
| Variable | Required | Default | Description |
|---|---|---|---|
SMTP_HOST | No | --- | SMTP server hostname |
SMTP_PORT | No | 587 | SMTP server port |
SMTP_SECURE | No | false | Use TLS (true for port 465) |
SMTP_USER | No | --- | SMTP username |
SMTP_PASS | No | --- | SMTP password or API key |
EMAIL_FROM | No | [email protected] | Sender email address |
Application URLs
| Variable | Required | Default | Description |
|---|---|---|---|
FRONTEND_URL | No | http://localhost:3000 | Frontend origin (scheme + host + port, no trailing slash) |
ADMIN_URL | No | http://localhost:3000 | Admin origin (usually same as FRONTEND_URL) |
API_URL | No | http://localhost:3001 | Public API URL for email assets |
VITE_API_URL | No | /api | Frontend API base path (keep /api for Docker) |
Ports
| Variable | Required | Default | Description |
|---|---|---|---|
BACKEND_PORT | No | 3001 | Host port for the backend |
FRONTEND_PORT | No | 3000 | Host port for the frontend |
DB_PORT | No | 5432 | Host port for PostgreSQL |
REDIS_PORT | No | 6379 | Host port for Redis |
Storage
| Variable | Required | Default | Description |
|---|---|---|---|
APP_STORAGE | No | ./storage | Host path for storage |
APP_DATA | No | ./data | Host path for application data |
LOGS | No | ./logs | Host path for logs |
EXTERNAL_MEDIA_ROOT | No | Auto-detected or set by Compose | Library path visible to the backend process. In Docker, set this to the container-side path of the read-only mount; see External Media configuration. |
External-folder watching
Available in builds containing PicPeak PR #1345 . These settings control automatic imports for reference-mode events. Each event must also opt in using Watch folder for new files; the global default does not enable watching for every event. See External Media for setup, permissions and deletion behaviour.
| Variable | Required | Default | Description |
|---|---|---|---|
EXTERNAL_MEDIA_WATCH | No | true | Global service switch. Set false to stop automatic external imports for all events; saved per-event watch settings remain unchanged. |
EXTERNAL_MEDIA_WATCH_POLLING | No | false | Use repeated filesystem metadata checks instead of notifications. Useful for NFS/SMB shares that do not report writes from other machines. |
EXTERNAL_MEDIA_WATCH_POLL_INTERVAL_MS | No | 5000 | File polling interval when polling is enabled (5 seconds). |
EXTERNAL_MEDIA_WATCH_SWEEP_INTERVAL_MS | No | 900000 | Full scan of watched folders every 15 minutes as a fallback. 0 disables this scan. |
EXTERNAL_MEDIA_WATCH_DEBOUNCE_MS | No | 10000 | Quiet period after the last detected new file before starting an import (10 seconds). |
EXTERNAL_MEDIA_WATCH_STABILITY_MS | No | 5000 | Settling interval for new files (5 seconds). Automatic passes defer recently modified or changing files and retry later. |
EXTERNAL_MEDIA_WATCH_RECONCILE_INTERVAL_MS | No | 60000 | Advanced: how often the backend re-reads event watch settings (1 minute). Add this explicitly to Compose’s backend environment block if overriding it. |
All interval values are in milliseconds. The updated bundled Compose files forward the first six options from .env; they do not forward EXTERNAL_MEDIA_WATCH_RECONCILE_INTERVAL_MS. Custom Compose definitions must pass the desired variables through environment or env_file. On AIO, set them on the PicPeak container. Recreate the container after changes; for a native process, restart the backend with the new environment.
The watcher uses the local EXTERNAL_MEDIA_ROOT with both local and S3 storage backends. These settings do not configure the managed-storage file watcher or the S3 prefix walker.
Docker user mapping
| Variable | Required | Default | Description |
|---|---|---|---|
PUID | No | 1001 | Container user UID |
PGID | No | 1001 | Container user GID |
Social link preview fallback
Used for the static HTML title and meta description on the SPA shell — the fallback that catches link-preview fetchers (WhatsApp Business API, Twilio, LinkPreview, etc.) that don’t trigger the per-event Open Graph endpoint. Substituted into index.html at frontend-container start, so changes take effect on the next docker compose up -d frontend — no frontend rebuild needed.
| Variable | Required | Default | Description |
|---|---|---|---|
BRAND_TITLE | No | PicPeak | Title shown when a non-crawler fetcher reads index.html |
BRAND_DESCRIPTION | No | Photo gallery shared with PicPeak. | Meta description / og:description for the same fallback |
Release channel
| Variable | Required | Default | Description |
|---|---|---|---|
PICPEAK_CHANNEL | No | stable | Image tag: stable, beta, or a specific version |
UPDATE_CHECK_ENABLED | No | true | Show update notifications in admin dashboard |
Analytics
| Variable | Required | Default | Description |
|---|---|---|---|
VITE_UMAMI_URL | No | --- | Umami analytics server URL |
VITE_UMAMI_WEBSITE_ID | No | --- | Umami website ID |
VITE_UMAMI_SHARE_URL | No | --- | Umami public share URL |
Public landing page
| Variable | Required | Default | Description |
|---|---|---|---|
PUBLIC_SITE_CACHE_TTL_MS | No | 60000 | Cache TTL for the landing page in milliseconds |
Storage backend
See Storage Backends for the migration playbook and provider-specific examples.
| Variable | Required | Default | Description |
|---|---|---|---|
STORAGE_BACKEND | No | local | local or s3 |
STORAGE_S3_BUCKET | When s3 | --- | Bucket name |
STORAGE_S3_REGION | No | us-east-1 | AWS region; use auto for R2 |
STORAGE_S3_ACCESS_KEY | When s3 | --- | Access key ID |
STORAGE_S3_SECRET_KEY | When s3 | --- | Secret access key |
STORAGE_S3_ENDPOINT | No | --- | Custom endpoint (MinIO, R2, B2, Spaces…) |
STORAGE_S3_PREFIX | No | --- | Namespace inside the bucket |
STORAGE_S3_FORCE_PATH_STYLE | No | auto when endpoint set | true for MinIO and IP-based endpoints |
STORAGE_S3_SSL | No | true | false for plain-HTTP endpoints (dev MinIO) |
STORAGE_AUTO_IMPORT | No | false | Enable the S3 prefix walker (replaces chokidar in S3 mode) |
STORAGE_AUTO_IMPORT_INTERVAL_MS | No | 300000 | Walker poll interval (5 minutes) |
Webhooks
See Webhooks for event catalog, payload shapes, and verification examples.
| Variable | Required | Default | Description |
|---|---|---|---|
WEBHOOK_ALLOW_PRIVATE_URLS | No | false | When true, allows webhooks to POST to private/loopback addresses. Production must leave this off. Dev only. |
WEBHOOK_DELIVERY_INTERVAL_MS | No | 5000 | How often the worker polls for pending deliveries |
WEBHOOK_DELIVERY_CONCURRENCY | No | 5 | Max in-flight deliveries per tick. Bump if your receivers are slow. |
WEBHOOK_HTTP_TIMEOUT_MS | No | 10000 | Per-request timeout |
WEBHOOK_MAX_ATTEMPTS | No | 5 | Total attempts before a delivery is marked failed |