From fc30702846baaa0aa7c0cc940473e390e623875b Mon Sep 17 00:00:00 2001 From: iven Date: Mon, 11 May 2026 10:27:38 +0800 Subject: [PATCH] =?UTF-8?q?feat(docker):=20PostgreSQL=20=E6=AF=8F=E6=97=A5?= =?UTF-8?q?=E8=87=AA=E5=8A=A8=E5=A4=87=E4=BB=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 backup.sh: pg_dump + gzip,自动清理过期备份 - production compose 添加 backup 服务: cron 每日 02:00 执行 - 可通过 BACKUP_CRON / BACKUP_KEEP_DAYS 环境变量自定义 --- docker/backup.sh | 43 +++++++++++++++ docker/docker-compose.production.yml | 79 ++++++++++++++++++++++++++++ 2 files changed, 122 insertions(+) create mode 100644 docker/backup.sh create mode 100644 docker/docker-compose.production.yml diff --git a/docker/backup.sh b/docker/backup.sh new file mode 100644 index 0000000..cc3e1e5 --- /dev/null +++ b/docker/backup.sh @@ -0,0 +1,43 @@ +#!/usr/bin/env bash +# PostgreSQL 自动备份脚本 +# 用法: +# 手动: ./docker/backup.sh +# 自动: 由 docker compose backup 服务每日 02:00 执行 +set -euo pipefail + +BACKUP_DIR="${BACKUP_DIR:-/backups}" +PG_HOST="${PGHOST:-postgres}" +PG_PORT="${PGPORT:-5432}" +PG_USER="${PGUSER:-erp}" +PG_DB="${PGDATABSE:-erp}" +KEEP_DAYS="${KEEP_DAYS:-7}" +TIMESTAMP=$(date +%Y%m%d_%H%M%S) +FILENAME="${PG_DB}_${TIMESTAMP}.sql.gz" +FILEPATH="${BACKUP_DIR}/${FILENAME}" + +mkdir -p "${BACKUP_DIR}" + +echo "[$(date -Iseconds)] 开始备份 ${PG_DB} → ${FILEPATH}" + +if pg_dump \ + -h "${PG_HOST}" \ + -p "${PG_PORT}" \ + -U "${PG_USER}" \ + -d "${PG_DB}" \ + --format=plain \ + --no-owner \ + --no-privileges \ + | gzip > "${FILEPATH}"; then + SIZE=$(du -h "${FILEPATH}" | cut -f1) + echo "[$(date -Iseconds)] 备份完成: ${FILENAME} (${SIZE})" +else + echo "[$(date -Iseconds)] 备份失败!" >&2 + rm -f "${FILEPATH}" + exit 1 +fi + +# 清理过期备份 +DELETED=$(find "${BACKUP_DIR}" -name "${PG_DB}_*.sql.gz" -mtime +${KEEP_DAYS} -delete -print | wc -l) +if [ "${DELETED}" -gt 0 ]; then + echo "[$(date -Iseconds)] 已清理 ${DELETED} 个过期备份(>${KEEP_DAYS}天)" +fi diff --git a/docker/docker-compose.production.yml b/docker/docker-compose.production.yml new file mode 100644 index 0000000..efe9a5b --- /dev/null +++ b/docker/docker-compose.production.yml @@ -0,0 +1,79 @@ +# 生产环境 Docker Compose 配置 +# 使用方式: docker compose -f docker/docker-compose.yml -f docker/docker-compose.production.yml up -d + +services: + app: + build: + context: .. + dockerfile: Dockerfile + container_name: hms-server + restart: unless-stopped + ports: + - "${APP_PORT:-3000}:3000" + - "${METRICS_PORT:-9090}:9090" + env_file: + - .env.production + environment: + ERP__DATABASE__URL: postgres://${POSTGRES_USER:-erp}:${POSTGRES_PASSWORD}@postgres:${POSTGRES_PORT:-5432}/${POSTGRES_DB:-erp} + ERP__REDIS__URL: redis://:${REDIS_PASSWORD}@redis:${REDIS_PORT:-6379} + volumes: + - app-uploads:/app/uploads + depends_on: + postgres: + condition: service_healthy + redis: + condition: service_healthy + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:3000/api/v1/health"] + interval: 30s + timeout: 5s + start_period: 60s + retries: 3 + deploy: + resources: + limits: + cpus: "2" + memory: 1024M + reservations: + cpus: "0.5" + memory: 256M + networks: + - hms-internal + + # 每日自动备份 — 每天凌晨 02:00 执行 pg_dump,保留 7 天 + # 手动触发: docker compose -f docker/docker-compose.yml -f docker/docker-compose.production.yml run --rm backup + backup: + image: postgres:16-alpine + container_name: hms-backup + restart: unless-stopped + entrypoint: > + sh -c " + echo '$$BACKUP_CRON /usr/local/bin/backup.sh' > /etc/crontabs/root && + crond -f -l 2 + " + environment: + PGHOST: postgres + PGPORT: "${POSTGRES_PORT:-5432}" + PGUSER: "${POSTGRES_USER:-erp}" + PGDATABASE: "${POSTGRES_DB:-erp}" + BACKUP_DIR: /backups + KEEP_DAYS: "${BACKUP_KEEP_DAYS:-7}" + BACKUP_CRON: "${BACKUP_CRON:-0 2 * * *}" + volumes: + - ./backup.sh:/usr/local/bin/backup.sh:ro + - backup_data:/backups + depends_on: + postgres: + condition: service_healthy + networks: + - hms-internal + +volumes: + app-uploads: + driver: local + backup_data: + driver: local + +networks: + hms-internal: + driver: bridge