mirror of
https://github.com/johannesjo/super-productivity.git
synced 2026-07-18 00:46:45 +00:00
186 lines
5.6 KiB
YAML
186 lines
5.6 KiB
YAML
# Production docker-compose for super-sync-server
|
||
#
|
||
# Usage:
|
||
# 1. Copy env.example to .env and configure your settings
|
||
# 2. Run: ./scripts/deploy.sh
|
||
# 3. View logs: docker-compose logs -f
|
||
#
|
||
# For local builds (development):
|
||
# ./scripts/deploy.sh --build
|
||
|
||
x-logging: &default-logging
|
||
driver: 'json-file'
|
||
options:
|
||
max-size: '10m'
|
||
max-file: '3'
|
||
|
||
services:
|
||
supersync:
|
||
image: ${SUPERSYNC_IMAGE:-ghcr.io/super-productivity/supersync:latest}
|
||
container_name: supersync-server
|
||
restart: unless-stopped
|
||
environment:
|
||
- NODE_ENV=${NODE_ENV:-production}
|
||
- PORT=1900
|
||
- HOST=${HOST:-0.0.0.0}
|
||
- DATABASE_URL=${DATABASE_URL:-postgresql://${POSTGRES_USER:-supersync}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB:-supersync}}
|
||
- RUN_MIGRATIONS_ON_STARTUP=${RUN_MIGRATIONS_ON_STARTUP:-false}
|
||
- JWT_SECRET=${JWT_SECRET}
|
||
- PUBLIC_URL=${PUBLIC_URL:-http://localhost:1900}
|
||
- CORS_ORIGINS=${CORS_ORIGINS:-https://app.super-productivity.com}
|
||
- SMTP_HOST=${SMTP_HOST:-}
|
||
- SMTP_PORT=${SMTP_PORT:-587}
|
||
- SMTP_SECURE=${SMTP_SECURE:-false}
|
||
- SMTP_USER=${SMTP_USER:-}
|
||
- SMTP_PASS=${SMTP_PASS:-}
|
||
- SMTP_FROM=${SMTP_FROM:-}
|
||
- WEBAUTHN_RP_ID=${WEBAUTHN_RP_ID:-localhost}
|
||
- WEBAUTHN_RP_NAME=${WEBAUTHN_RP_NAME:-Super Productivity Sync}
|
||
- WEBAUTHN_ORIGIN=${WEBAUTHN_ORIGIN:-http://localhost:1900}
|
||
- OLD_OPS_CLEANUP_DELETE_BATCH_SIZE=${OLD_OPS_CLEANUP_DELETE_BATCH_SIZE:-5000}
|
||
- OLD_OPS_CLEANUP_MAX_DELETED_PER_RUN=${OLD_OPS_CLEANUP_MAX_DELETED_PER_RUN:-25000}
|
||
- SUPERSYNC_BATCH_UPLOAD=${SUPERSYNC_BATCH_UPLOAD:-false}
|
||
- SUPERSYNC_PAYLOAD_BYTES_BACKFILL_COMPLETE=${SUPERSYNC_PAYLOAD_BYTES_BACKFILL_COMPLETE:-false}
|
||
ports:
|
||
- '127.0.0.1:1900:1900'
|
||
volumes:
|
||
- supersync-data:/app/data
|
||
depends_on:
|
||
postgres:
|
||
condition: service_healthy
|
||
healthcheck:
|
||
test:
|
||
[
|
||
'CMD',
|
||
'wget',
|
||
'--no-verbose',
|
||
'--tries=1',
|
||
'--spider',
|
||
'http://localhost:1900/health',
|
||
]
|
||
interval: 30s
|
||
timeout: 5s
|
||
retries: 3
|
||
start_period: 30s
|
||
security_opt:
|
||
- no-new-privileges:true
|
||
cap_drop:
|
||
- ALL
|
||
# 200+ WSS clients each consume an fd; Alpine default soft limit (1024)
|
||
# would be tight under reconnect bursts.
|
||
ulimits:
|
||
nofile:
|
||
soft: 65535
|
||
hard: 65535
|
||
mem_limit: 768m
|
||
cpus: '1.0'
|
||
networks:
|
||
- internal
|
||
logging: *default-logging
|
||
|
||
postgres:
|
||
image: postgres:16-alpine
|
||
container_name: supersync-postgres
|
||
restart: unless-stopped
|
||
environment:
|
||
- POSTGRES_USER=${POSTGRES_USER:-supersync}
|
||
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
|
||
- POSTGRES_DB=${POSTGRES_DB:-supersync}
|
||
# Tuned for the 1.5g mem_limit. shared_buffers needs shm_size > default 64m.
|
||
# 120 × 4MB work_mem = 480MB worst-case sort budget + 384MB shared_buffers,
|
||
# well inside the 1.5g cap. Prisma's DEFAULT pool is ~(host_cpu_count × 2),
|
||
# read from host cores NOT the container cpu limit — it is NOT single-digit
|
||
# on this VPS and will silently consume the entire cap. The app MUST bound
|
||
# it via DATABASE_URL ?connection_limit=N (currently 60). max_connections
|
||
# stays well above N to reserve slots for migrations, psql, the old-ops
|
||
# cleanup job, and the reconnect stampede after an in-place crash recovery.
|
||
command:
|
||
- postgres
|
||
- -c
|
||
- shared_buffers=384MB
|
||
- -c
|
||
- effective_cache_size=1GB
|
||
- -c
|
||
- work_mem=4MB
|
||
- -c
|
||
- maintenance_work_mem=128MB
|
||
- -c
|
||
- max_connections=120
|
||
- -c
|
||
- random_page_cost=1.1
|
||
- -c
|
||
# 5 min — kill leaked Prisma idle-in-tx, but well above the
|
||
# migrate-deploy.sh 30-min STEP_TIMEOUT for CONCURRENTLY index builds.
|
||
- idle_in_transaction_session_timeout=300000
|
||
- -c
|
||
- wal_compression=on
|
||
- -c
|
||
- huge_pages=off
|
||
shm_size: 256m
|
||
volumes:
|
||
- postgres-data:/var/lib/postgresql/data
|
||
healthcheck:
|
||
test:
|
||
- CMD-SHELL
|
||
- >
|
||
pg_isready -U "$$POSTGRES_USER" -d "$$POSTGRES_DB" &&
|
||
psql -U "$$POSTGRES_USER" -d "$$POSTGRES_DB" -c "SELECT 1" > /dev/null 2>&1
|
||
interval: 10s
|
||
timeout: 5s
|
||
retries: 5
|
||
start_period: 10s
|
||
security_opt:
|
||
- no-new-privileges:true
|
||
mem_limit: 1536m
|
||
networks:
|
||
internal:
|
||
aliases:
|
||
# Keep existing .env files with DATABASE_URL=...@db:5432/... working.
|
||
- db
|
||
logging: *default-logging
|
||
|
||
caddy:
|
||
image: caddy:2.11-alpine
|
||
container_name: supersync-caddy
|
||
restart: unless-stopped
|
||
ports:
|
||
- '80:80'
|
||
- '443:443'
|
||
volumes:
|
||
- ./Caddyfile:/etc/caddy/Caddyfile:ro
|
||
- caddy-data:/data
|
||
- caddy-config:/config
|
||
environment:
|
||
- DOMAIN=${DOMAIN}
|
||
healthcheck:
|
||
# Use 127.0.0.1, not localhost: Caddy binds the admin API to IPv4
|
||
# 127.0.0.1:2019, but busybox wget resolves `localhost` to ::1 first
|
||
# and gets "connection refused", marking a healthy Caddy unhealthy.
|
||
test: ['CMD', 'wget', '-q', '--spider', 'http://127.0.0.1:2019/config/']
|
||
interval: 30s
|
||
timeout: 5s
|
||
retries: 3
|
||
start_period: 10s
|
||
networks:
|
||
- internal
|
||
cap_drop:
|
||
- ALL
|
||
cap_add:
|
||
- NET_BIND_SERVICE
|
||
mem_limit: 256m
|
||
depends_on:
|
||
- supersync
|
||
logging: *default-logging
|
||
|
||
volumes:
|
||
supersync-data:
|
||
postgres-data:
|
||
caddy-data:
|
||
caddy-config:
|
||
|
||
networks:
|
||
internal:
|
||
# Pinned name so docker-compose.monitoring.yml can attach via
|
||
# `external: true` regardless of the COMPOSE_PROJECT_NAME used.
|
||
name: super-sync-server_internal
|
||
driver: bridge
|