Files
pad/docker-compose.prod.yml
T
xarmian 5fffee2b9e fix(docker): bind to 127.0.0.1 + require POSTGRES_PASSWORD (TASK-661) (#174)
* fix(docker): bind to 127.0.0.1 + require POSTGRES_PASSWORD (TASK-661)

A fresh Docker install previously published 7777 on 0.0.0.0 with a
hardcoded pad:pad Postgres credential. The bootstrap endpoint is
reachable until the first admin is created, so this combination lets
anyone who can route to the host claim the instance — and with M5's
X-Forwarded-For spoof (fixed in TASK-660) chained with the loopback
bootstrap check, it became a full takeover.

Changes:
- docker-compose.yml: publish "127.0.0.1:7777:7777" by default, with a
  PAD_BIND_ADDR override for operators who intentionally want LAN
  access. Require POSTGRES_PASSWORD via ${VAR:?err} so docker compose
  refuses to start when it's unset — can't silently inherit a weak
  default credential.
- docker-compose.prod.yml: drop the "change-me-in-production"
  placeholder; require the same env var as the base file.
- .env.example: new file documenting POSTGRES_PASSWORD (required),
  PAD_BIND_ADDR, REDIS_PASSWORD, PAD_CLOUD_SECRET, PAD_ENCRYPTION_KEY,
  PAD_TRUSTED_PROXIES with generation instructions.
- README.md: add a Docker Compose section covering the .env workflow
  and the loopback-default → LAN override.

Parent: PLAN-643 (OSS Security Hardening).

* fix(docker): use libpq keyword=value DSN to avoid URI-encoding the Postgres password per Codex P1

Passwords produced by 'openssl rand -base64' often include '/', '+', or ':'
which are reserved in URI userinfo. Injecting them into postgres://user:PASS@...
breaks sql.Open. Switch PAD_DATABASE_URL to the libpq keyword=value form
(host=... password=... dbname=...) where the password is parsed as a single
token regardless of special characters.

Also teach pgDbnameFromURL to parse both DSN shapes so 'pad db backup/restore'
still shows the correct database name in its confirmation prompt.
2026-04-21 19:14:15 -04:00

82 lines
2.6 KiB
YAML

# Pad — production Docker Compose override
# Usage: docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d
#
# Extends the base docker-compose.yml with:
# - Resource limits
# - Secure cookie settings
# - CORS configuration
# - Email (Maileroo) support
# - Named network for reverse proxy integration
services:
pad:
environment:
PAD_SECURE_COOKIES: "true"
# Override Redis URL to include password when REDIS_PASSWORD is set.
# Without this, the pad container inherits the passwordless URL from
# docker-compose.yml and fails to connect when Redis AUTH is enabled.
PAD_REDIS_URL: "redis://:${REDIS_PASSWORD:-}@redis:6379"
# Set your public-facing URL for correct invitation links:
# PAD_URL: "https://pad.example.com"
# CORS origins (comma-separated):
# PAD_CORS_ORIGINS: "https://pad.example.com"
# Email (Maileroo):
# PAD_MAILEROO_API_KEY: "your-sending-key"
# PAD_EMAIL_FROM: "noreply@example.com"
# PAD_EMAIL_FROM_NAME: "Pad"
# SSE limits:
# PAD_SSE_MAX_CONNECTIONS: "1000"
# PAD_SSE_MAX_PER_WORKSPACE: "100"
deploy:
resources:
limits:
cpus: "2.0"
memory: 512M
reservations:
cpus: "0.5"
memory: 128M
networks:
- pad-net
- default
postgres:
environment:
# Base compose already requires POSTGRES_PASSWORD. We keep the same
# required-reference here (no placeholder default) so a production
# deploy can never silently boot with a known-weak credential.
POSTGRES_PASSWORD: "${POSTGRES_PASSWORD:?POSTGRES_PASSWORD is required — see .env.example}"
deploy:
resources:
limits:
cpus: "2.0"
memory: 1G
reservations:
cpus: "0.25"
memory: 256M
# In production, consider using a managed PostgreSQL service instead.
redis:
command: redis-server --maxmemory 128mb --maxmemory-policy allkeys-lru --requirepass "${REDIS_PASSWORD:-}"
healthcheck:
# Override the base healthcheck to authenticate when REDIS_PASSWORD is set.
# redis-cli reads REDISCLI_AUTH automatically for authentication.
test: ["CMD-SHELL", "REDISCLI_AUTH=$${REDIS_PASSWORD:-} redis-cli ping | grep -q PONG"]
interval: 5s
timeout: 3s
retries: 5
environment:
REDIS_PASSWORD: "${REDIS_PASSWORD:-}"
deploy:
resources:
limits:
cpus: "1.0"
memory: 256M
reservations:
cpus: "0.1"
memory: 64M
networks:
pad-net:
name: pad-net
# Attach your reverse proxy (Caddy, nginx) to this network.