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 |
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 | /app/storage/external-media | Path to external photo library |
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 |