# Pad — Docker Compose environment example # # Copy to .env, fill in values, then run `docker compose up -d`. # # If you don't already have a POSTGRES_PASSWORD, generate one with: # openssl rand -base64 32 # …and paste it below. Docker Compose WILL refuse to start with the password # missing; this is deliberate so a fresh deploy can never inherit a known-weak # default credential. # REQUIRED — PostgreSQL password for the `pad` user. POSTGRES_PASSWORD= # REQUIRED on Postgres — AES-256 encryption key for TOTP seeds and other # sensitive fields. 64-character hex string (32 bytes). Pad will auto- # generate one on SQLite deployments but refuses to on Postgres because # multi-replica deployments would generate divergent keys and fail cross- # instance decryption. Generate with: # openssl rand -hex 32 PAD_ENCRYPTION_KEY= # OPTIONAL — Which host interface the Pad web UI publishes to. # Default (loopback only) is the safest choice on a fresh install, because # the bootstrap endpoint is reachable until the first admin is created. # 127.0.0.1 → localhost only (default) # 0.0.0.0 → every interface (LAN + internet if no firewall) # 10.0.0.5 → bind to a specific LAN IP # PAD_BIND_ADDR=127.0.0.1 # OPTIONAL — Redis auth (docker-compose.prod.yml only). # If set, Redis starts with `--requirepass` and Pad connects with the password. # REDIS_PASSWORD= # OPTIONAL — Pad Cloud sidecar shared secret (cloud mode only). # Comma-separated list supports rotation for INBOUND calls: pad accepts # any listed secret when pad-cloud calls pad's admin endpoints. # PAD_CLOUD_SECRET= # OPTIONAL — Base URL pad uses to call pad-cloud back (reverse direction, # e.g. Stripe cancel-customer when a user deletes their account). Required # in cloud mode to cascade account deletion through to Stripe billing; # without it, pad deletes local rows but never cancels the subscription. # PAD_CLOUD_SIDECAR_URL=http://pad-cloud:7778 # OPTIONAL — Exact outbound secret pad sends on reverse calls. Use this # during a cloud-secret rotation when pad-cloud is still validating against # the OLD value: pin the outbound here so the reverse call matches, then # roll pad-cloud, then unpin. When unset, pad falls back to the LAST entry # of PAD_CLOUD_SECRET (the older rotation value). # PAD_CLOUD_OUTBOUND_SECRET= # OPTIONAL — Encryption key for sensitive fields (32-byte hex string). # If unset, Pad stores 2FA seeds in plaintext — set this for production. # openssl rand -hex 32 # PAD_ENCRYPTION_KEY= # OPTIONAL — Trusted reverse-proxy CIDRs. Only peers in these CIDRs may set # X-Forwarded-For / X-Real-IP. Leave unset for direct-exposed deploys. # PAD_TRUSTED_PROXIES=10.0.0.0/8,172.16.0.0/12 # OPTIONAL — Bearer token required to scrape /metrics. When unset, /metrics # is reachable only from loopback (so a Prometheus on the same host keeps # working without config); when set, every scrape must send # "Authorization: Bearer $PAD_METRICS_TOKEN". # openssl rand -hex 32 # PAD_METRICS_TOKEN= # OPTIONAL — Session IP-change policy. # Default (unset) logs an ActionSessionIPChanged audit entry when a session # presents a new client IP and updates the stored IP. "strict" additionally # destroys the session and forces re-login. Strict mode breaks legitimate # mobility (mobile roaming, VPN toggles, carrier NAT); enable only for # high-sensitivity deployments. # PAD_IP_CHANGE_ENFORCE=strict # ───────────────────────────────────────────────────────────────────── # Core server — see docs/deployment.md for full reference. # ───────────────────────────────────────────────────────────────────── # Listen address. 0.0.0.0 for containers, 127.0.0.1 for loopback-only. # PAD_HOST=0.0.0.0 # Listen port. # PAD_PORT=7777 # Public-facing base URL. Used to build invitation + password-reset links. # PAD_URL=https://pad.example.com # Writable data directory — SQLite DB, config, logs. Default: ~/.pad # PAD_DATA_DIR=/data # Log level: debug | info | warn | error # PAD_LOG_LEVEL=info # Operating mode: local | remote | cloud. Controls how the CLI # discovers the daemon and which credential keying rules apply. # PAD_MODE=local # ───────────────────────────────────────────────────────────────────── # Database — SQLite default; set PAD_DB_DRIVER=postgres for Postgres. # ───────────────────────────────────────────────────────────────────── # PAD_DB_DRIVER=postgres # PAD_DB_PATH=/data/pad.db # SQLite path, ignored on Postgres # PAD_DATABASE_URL=postgres://pad:secret@postgres:5432/pad?sslmode=disable # ───────────────────────────────────────────────────────────────────── # Real-time events — cross-instance SSE fan-out via Redis pub/sub. # Without this, SSE stays in-process (fine for single-node). # ───────────────────────────────────────────────────────────────────── # PAD_REDIS_URL=redis://redis:6379 # Global cap on concurrent SSE connections (default: 1000). # PAD_SSE_MAX_CONNECTIONS=1000 # Per-workspace cap on concurrent SSE connections (default: 100). # PAD_SSE_MAX_PER_WORKSPACE=100 # ───────────────────────────────────────────────────────────────────── # Security hardening for production behind TLS. # ───────────────────────────────────────────────────────────────────── # Set Secure flag on session cookies — requires TLS. # PAD_SECURE_COOKIES=true # Comma-separated list of allowed CORS origins. Leave unset to disable CORS. # PAD_CORS_ORIGINS=https://pad.example.com,https://app.example.com # ───────────────────────────────────────────────────────────────────── # Transactional email via Maileroo. Optional — without it, workspace # invites fall back to CLI-copyable join codes. # ───────────────────────────────────────────────────────────────────── # PAD_MAILEROO_API_KEY=your-sending-key # PAD_EMAIL_FROM=noreply@example.com # PAD_EMAIL_FROM_NAME=Pad