mirror of
https://github.com/PerpetualSoftware/pad.git
synced 2026-10-25 05:49:50 +00:00
c50f0585ad
* fix(collab): WebSocket dials get their own per-user bucket and a per-user socket cap; the web provider jitters its reconnects (BUG-1308) A collab dial drew on the general per-user API bucket (burst 60), so 100 dials from one user got 60 through, and a user's own dials and REST calls refused each other. Dials now draw on a CollabDial bucket (5/s, burst 50), and how many sockets a principal may hold is bounded separately by PAD_COLLAB_MAX_PER_USER (default 50), refused with 429 collab_limit_exceeded and Retry-After. Both defaults are sized from the reconnect-storm measurement on BUG-1308's trail (checkpoint 2). A browser cannot read a refused handshake's status, so the provider's backoff is what spaces a refused herd out: each step is now jittered to between half and all of itself. Claude-Session: https://claude.ai/code/session_01RYiqbqXmxQMoKHsGm8B77N * docs(collab): PAD_COLLAB_MAX_PER_USER and the collab dial bucket, in the deployment docs, samples and load-test usage (BUG-1308) Claude-Session: https://claude.ai/code/session_01RYiqbqXmxQMoKHsGm8B77N * fix(collab): only a WebSocket upgrade is a dial; per-user cap pinned within one workspace; dial-bucket keying stated correctly (BUG-1308, codex round 1) Claude-Session: https://claude.ai/code/session_01RYiqbqXmxQMoKHsGm8B77N * docs(collab): a PAD_COLLAB_MAX_PER_USER below 1 means unlimited, as the SSE knobs do (BUG-1308, codex round 2) Claude-Session: https://claude.ai/code/session_01RYiqbqXmxQMoKHsGm8B77N * docs(collab): the struct field agrees, 0 or less is unlimited (BUG-1308, codex round 3) Claude-Session: https://claude.ai/code/session_01RYiqbqXmxQMoKHsGm8B77N
97 lines
3.5 KiB
YAML
97 lines
3.5 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 covers BOTH SSE endpoints (BUG-2726).
|
|
# PAD_SSE_MAX_CONNECTIONS: "1000"
|
|
# PAD_SSE_MAX_PER_WORKSPACE: "100"
|
|
# PAD_SSE_MAX_PER_USER: "50"
|
|
# Open collab WebSockets per user, per instance (BUG-1308).
|
|
# PAD_COLLAB_MAX_PER_USER: "50"
|
|
# Set only when this Redis is shared with another Pad installation.
|
|
# PAD_REDIS_NAMESPACE: "prod"
|
|
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:
|
|
# noeviction, NOT allkeys-lru (BUG-2698). Pad's Redis holds the
|
|
# session-presence registry as well as pub/sub, and evicting a live
|
|
# session's entry is indistinguishable from its TTL lapsing: the
|
|
# session disappears from the "push to agent" picker and a push
|
|
# targeted at it reports delivered_sessions: 0 while it is still
|
|
# connected. It self-repairs on the next 30s renewal, but the
|
|
# keyspace is small — a few hundred bytes per connected session plus
|
|
# two counters — so there is nothing to gain by evicting it. See
|
|
# docs/deployment.md's Redis configuration notes.
|
|
command: redis-server --maxmemory 128mb --maxmemory-policy noeviction --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.
|