Files
gochat/deploy/docker/docker-compose.prod.yml
T
Rogeeandrogee 6d6d80dd86 HH-441: harden durable storage and recovery (#92)
* HH-441: harden durable storage and recovery

* HH-441: clear recovery review blockers

* HH-441: enforce offsite backup failure domain

---------

Co-authored-by: Rogee <rogee@ipao.vip>
2026-08-22 02:16:02 +08:00

236 lines
8.0 KiB
YAML

name: gochat-production
x-gochat-image: &gochat-image ${GOCHAT_IMAGE_REF:?set GOCHAT_IMAGE_REF to an immutable image digest}
x-gochat-environment: &gochat-environment
GOCHAT_ENV: production
GOCHAT_SERVER_HOST: 0.0.0.0
GOCHAT_SERVER_PORT: 3000
GOCHAT_SERVER_MODE: release
GOCHAT_SERVER_CORS_ALLOWED_ORIGINS: ${GOCHAT_SERVER_CORS_ALLOWED_ORIGINS:?set production CORS origins}
GOCHAT_DATABASE_DSN: ${GOCHAT_DATABASE_DSN:-postgres://${POSTGRES_USER:-gochat}:${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB:-gochat_production}?sslmode=disable}
GOCHAT_DATABASE_RUN_MIGRATIONS: "false"
GOCHAT_DATABASE_MIGRATIONS_PATH: /app/migrations
GOCHAT_REDIS_DSN: redis://:${REDIS_PASSWORD:?set REDIS_PASSWORD}@redis:6379
GOCHAT_SEARCH_ENGINE: meilisearch
GOCHAT_SEARCH_HOST: http://meilisearch:7700
GOCHAT_SEARCH_API_KEY: ${MEILI_MASTER_KEY:?set MEILI_MASTER_KEY}
GOCHAT_JWT_SECRET: ${GOCHAT_JWT_SECRET:?set GOCHAT_JWT_SECRET}
GOCHAT_JWT_PREVIOUS_SECRETS: ${GOCHAT_JWT_PREVIOUS_SECRETS:-}
GOCHAT_LOG_LEVEL: info
GOCHAT_LOG_FORMAT: json
GOCHAT_STORAGE_PROVIDER: local
GOCHAT_STORAGE_LOCAL_PATH: /app/storage/uploads
services:
postgres:
image: ${POSTGRES_IMAGE_REF:-pgvector/pgvector:pg16@sha256:ccc6e83d6e35e931dc7c5def2022729d5a6c370318d099181995567ff1fb4d6b}
restart: always
environment:
POSTGRES_DB: ${POSTGRES_DB:-gochat_production}
POSTGRES_USER: ${POSTGRES_USER:-gochat}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD}
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
interval: 5s
timeout: 5s
retries: 20
deploy:
resources:
limits:
memory: 1G
redis:
image: ${REDIS_IMAGE_REF:-redis:7-alpine@sha256:ff02b58f971e7d7d156a1267e283fcbbeee91773b6aa36c49dac28ecfe28eadf}
restart: always
command: ["redis-server", "--requirepass", "${REDIS_PASSWORD:?set REDIS_PASSWORD}", "--appendonly", "yes", "--maxmemory", "512mb", "--maxmemory-policy", "allkeys-lru"]
environment:
REDIS_PASSWORD: ${REDIS_PASSWORD:?set REDIS_PASSWORD}
volumes:
- redis_data:/data
healthcheck:
test: ["CMD-SHELL", "redis-cli -a '$${REDIS_PASSWORD}' ping"]
interval: 5s
timeout: 5s
retries: 20
deploy:
resources:
limits:
memory: 512M
meilisearch:
image: ${MEILI_IMAGE_REF:-getmeili/meilisearch:v1.13@sha256:bed3fb650e62da53145777204891159242f6ea4ce69e215b36223af4aa64a0ae}
restart: always
environment:
MEILI_ENV: production
MEILI_MASTER_KEY: ${MEILI_MASTER_KEY:?set MEILI_MASTER_KEY}
MEILI_NO_ANALYTICS: "true"
volumes:
- meili_data:/meili_data
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--spider", "http://127.0.0.1:7700/health"]
interval: 5s
timeout: 5s
retries: 20
gochat:
image: *gochat-image
restart: always
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
meilisearch:
condition: service_healthy
environment: *gochat-environment
ports:
- "127.0.0.1:${GOCHAT_PORT:-3000}:3000"
volumes:
- gochat_storage:/app/storage
healthcheck:
test: ["CMD", "curl", "-fsS", "http://127.0.0.1:3000/health"]
interval: 10s
timeout: 5s
start_period: 15s
retries: 30
deploy:
resources:
limits:
memory: 512M
cpus: "1.0"
reservations:
memory: 256M
cpus: "0.5"
worker:
image: *gochat-image
restart: always
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
meilisearch:
condition: service_healthy
command: ["serve", "--worker-only"]
environment:
<<: *gochat-environment
GOCHAT_DATABASE_RUN_MIGRATIONS: "false"
volumes:
- gochat_storage:/app/storage
deploy:
resources:
limits:
memory: 512M
cpus: "1.0"
migrate:
image: *gochat-image
profiles: ["ops"]
depends_on:
postgres:
condition: service_healthy
entrypoint: ["/app/migrate"]
command: ["up"]
restart: "no"
environment:
<<: *gochat-environment
GOCHAT_DATABASE_DSN: postgres://${POSTGRES_USER:-gochat}:${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB:-gochat_production}?sslmode=disable&lock_timeout=5000&statement_timeout=900000
GOCHAT_DATABASE_RUN_MIGRATIONS: "false"
backup:
image: *gochat-image
user: "0:0"
profiles: ["ops"]
depends_on:
postgres:
condition: service_healthy
entrypoint: ["/app/scripts/db_backup.sh"]
restart: "no"
environment:
GOCHAT_DATABASE_DSN: postgres://${POSTGRES_USER:-gochat}:${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB:-gochat_production}?sslmode=disable
GOCHAT_STORAGE_PATH: /source/storage/uploads
GOCHAT_CONNECTOR_BACKUP_FILE: /source/connector/${GOCHAT_CONNECTOR_BACKUP_NAME:-latest.db}
GOCHAT_BACKUP_DIR: /backup/local
GOCHAT_BACKUP_OFFSITE_DIR: /backup/offsite
GOCHAT_BACKUP_PASSPHRASE_FILE: /run/secrets/backup-passphrase
GOCHAT_BACKUP_RETENTION_DAYS: ${GOCHAT_BACKUP_RETENTION_DAYS:-30}
GOCHAT_VERSION: ${GOCHAT_IMAGE_REF}
volumes:
- gochat_storage:/source/storage:ro
- shangwutong_backups:/source/connector:ro
- ${GOCHAT_BACKUP_DIR:-./backups/local}:/backup/local
- type: bind
source: ${GOCHAT_BACKUP_OFFSITE_DIR:?set an existing external off-site mount point}
target: /backup/offsite
bind:
create_host_path: false
- ${GOCHAT_BACKUP_PASSPHRASE_FILE:-./.secrets/backup-passphrase}:/run/secrets/backup-passphrase:ro
restore:
image: *gochat-image
user: "0:0"
profiles: ["ops"]
depends_on:
postgres:
condition: service_healthy
entrypoint: ["/app/scripts/db_restore.sh"]
command: ["/backup/offsite/${GOCHAT_RESTORE_BUNDLE:-missing.tar.enc}"]
restart: "no"
environment:
GOCHAT_DATABASE_DSN: postgres://${POSTGRES_USER:-gochat}:${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB:-gochat_production}?sslmode=disable
GOCHAT_STORAGE_PATH: /restore/storage/uploads
GOCHAT_CONNECTOR_DB_PATH: /restore/connector/connector.db
GOCHAT_BACKUP_PASSPHRASE_FILE: /run/secrets/backup-passphrase
volumes:
- gochat_storage:/restore/storage
- shangwutong_data:/restore/connector
- type: bind
source: ${GOCHAT_BACKUP_OFFSITE_DIR:?set an existing external off-site mount point}
target: /backup/offsite
read_only: true
bind:
create_host_path: false
- ${GOCHAT_BACKUP_PASSPHRASE_FILE:-./.secrets/backup-passphrase}:/run/secrets/backup-passphrase:ro
shangwutong:
image: ${SHANGWUTONG_IMAGE_REF:?set SHANGWUTONG_IMAGE_REF to an immutable image digest}
restart: always
stop_grace_period: ${SWT_SHUTDOWN_TIMEOUT:-30s}
environment:
SWT_CONNECTOR_LISTEN: :9100
SWT_CONNECTOR_DB_PATH: /data/connector.db
GOCHAT_BASE_URL: http://gochat:3000
GOCHAT_CONNECTOR_SERVICE_TOKEN: ${GOCHAT_CONNECTOR_SERVICE_TOKEN:-}
SWT_MAX_INFLIGHT_HEARTBEATS: ${SWT_MAX_INFLIGHT_HEARTBEATS:-64}
SWT_INBOUND_WORKERS: ${SWT_INBOUND_WORKERS:-8}
SWT_OUTBOUND_WORKERS: ${SWT_OUTBOUND_WORKERS:-8}
SWT_SHUTDOWN_TIMEOUT: ${SWT_SHUTDOWN_TIMEOUT:-30s}
volumes:
- shangwutong_data:/data
- shangwutong_backups:/backup
healthcheck:
test: ["CMD", "wget", "-q", "-T", "3", "-O", "/dev/null", "http://127.0.0.1:9100/readyz"]
interval: 30s
timeout: 5s
start_period: 15s
retries: 3
deploy:
resources:
limits:
memory: 512M
cpus: "1.0"
reservations:
memory: 128M
cpus: "0.25"
volumes:
postgres_data:
redis_data:
meili_data:
gochat_storage:
shangwutong_data:
shangwutong_backups: