# 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 — Container UID/GID for the pad process (Docker only). # Default: 1000/1000 with docker-compose; 99/100 with raw `docker run` # (Unraid `nobody:users` convention). Set these to match your host # volume's ownership to avoid permission errors on /data writes. # Must be POSITIVE integers — 0 (root) is rejected by the entrypoint. # See docker-entrypoint.sh. # PUID=1000 # PGID=1000 # 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. # Required on deployments that bind to 0.0.0.0 — without it, emailed links # point at "http://0.0.0.0:7777" which recipients cannot reach (BUG-899). # PAD_URL=https://pad.example.com # Alternative to PAD_URL using the generic env-var convention. Read by the # server only (does not flip the CLI to remote mode, does not contaminate # the CLI's BaseURL, and is intentionally NOT persisted to config.toml on # `pad init` / `pad configure` saves), so it's safe to set on hosts that # already use PUBLIC_URL for other tooling. Pad-cloud sets it on its # sidecar and forwards it to the pad service in the same compose. # Precedence: PAD_URL > PUBLIC_URL > constructed http://host:port. # PUBLIC_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 fan-out via Redis. Carries three things: # SSE activity events, watch/push notifications, and the session-presence # registry that `pad push` and the web UI's agent-session picker read. # Without this, all three stay in-process (fine for single-node). # Use a non-evicting maxmemory-policy — see docs/deployment.md. # ───────────────────────────────────────────────────────────────────── # PAD_REDIS_URL=redis://redis:6379 # Scopes every Redis key and channel to THIS installation. Leave unset # unless two Pad installations share one Redis endpoint — unset keeps the # historical key names, so upgrading changes nothing. Lowercase letters, # digits, '-' and '_' only; a whitespace-only value is refused at startup # rather than treated as unset. Changing it on a running deployment is a # cutover, not a tweak — see docs/deployment.md. # PAD_REDIS_NAMESPACE=prod # Streaming connection caps, all PER INSTANCE (no shared counter — a # three-replica deployment admits 3x these). MAX_CONNECTIONS and # MAX_PER_USER cover BOTH SSE endpoints; MAX_PER_WORKSPACE covers # /api/v1/events only. UPGRADING: MAX_CONNECTIONS used to bound only # /api/v1/events, so a tuned value now bounds more than it did. # Full detail, including the refusal contract: docs/deployment.md. # PAD_SSE_MAX_CONNECTIONS=1000 # PAD_SSE_MAX_PER_WORKSPACE=100 # PAD_SSE_MAX_PER_USER=50 # ───────────────────────────────────────────────────────────────────── # 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