# 只部署工资系统及其 MySQL/Redis;T+ 为客户现场的外部只读 SQL Server。 # 从仓库根目录执行,显式传 --env-file docker/.env.prod.local。 name: ys-payroll-prod x-app-environment: &app-environment TZ: Asia/Shanghai APP_ENV: prod APP_SECRET_KEY: ${APP_SECRET_KEY:?请设置应用密钥} MYSQL_HOST: mysql MYSQL_PORT: "3306" MYSQL_DATABASE: ${MYSQL_DATABASE:-payroll} MYSQL_USER: ${MYSQL_USER:-payroll} MYSQL_PASSWORD: ${MYSQL_PASSWORD:?请设置业务库密码} REDIS_HOST: redis REDIS_PORT: "6379" REDIS_PASSWORD: ${REDIS_PASSWORD:?请设置Redis密码} REDIS_DB: "0" T_PLUS_ENABLED: "true" T_PLUS_CONNECTION_FILE: "" T_PLUS_HOST: ${T_PLUS_HOST:?请填写客户现场T+服务器DNS名} T_PLUS_PORT: ${T_PLUS_PORT:-1433} T_PLUS_DATABASE: ${T_PLUS_DATABASE:?请确认客户账套数据库名} T_PLUS_USER: ${T_PLUS_USER:?请填写T+连接账号} T_PLUS_ALLOW_ADMIN_ACCOUNT: ${T_PLUS_ALLOW_ADMIN_ACCOUNT:-false} T_PLUS_PASSWORD: ${T_PLUS_PASSWORD:?请设置T+只读账号密码} T_PLUS_ODBC_DRIVER: "ODBC Driver 18 for SQL Server" T_PLUS_ENCRYPT: "true" T_PLUS_TRUST_SERVER_CERTIFICATE: "false" T_PLUS_AUTOMATION_ENABLED: ${T_PLUS_AUTOMATION_ENABLED:-false} T_PLUS_SCHEDULE_WEEKDAY: ${T_PLUS_SCHEDULE_WEEKDAY:-0} T_PLUS_SCHEDULE_TIME: ${T_PLUS_SCHEDULE_TIME:-02:00} PAYROLL_APPROVAL_ENABLED: ${PAYROLL_APPROVAL_ENABLED:-false} PAYROLL_ALLOW_LEGACY_CALCULATION: "false" x-app: &app image: ys-payroll-backend:${PAYROLL_VERSION:?请填写本次发布版本} build: context: .. dockerfile: backend/Dockerfile environment: *app-environment init: true restart: unless-stopped stop_grace_period: 15m depends_on: mysql: condition: service_healthy redis: condition: service_healthy logging: &bounded-logging driver: json-file options: max-size: "10m" max-file: "3" services: mysql: image: mysql:8.0 restart: unless-stopped environment: TZ: Asia/Shanghai MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:?请设置数据库管理密码} MYSQL_DATABASE: ${MYSQL_DATABASE:-payroll} MYSQL_USER: ${MYSQL_USER:-payroll} MYSQL_PASSWORD: ${MYSQL_PASSWORD:?请设置业务库密码} command: ["--character-set-server=utf8mb4", "--collation-server=utf8mb4_general_ci"] volumes: - mysql-data:/var/lib/mysql healthcheck: test: ["CMD-SHELL", "MYSQL_PWD=\"$$MYSQL_PASSWORD\" mysql --host=127.0.0.1 --user=\"$$MYSQL_USER\" --database=\"$$MYSQL_DATABASE\" --execute='SELECT 1' >/dev/null"] interval: 10s timeout: 5s retries: 12 start_period: 60s logging: *bounded-logging redis: image: redis:7.4-alpine restart: unless-stopped user: redis environment: REDIS_PASSWORD: ${REDIS_PASSWORD:?请设置Redis密码} # 仅将口令的 SHA-256 放入 Redis ACL,避免口令进入服务进程参数。 command: - /bin/sh - -ec - | password_hash=$$(printf '%s' "$$REDIS_PASSWORD" | sha256sum | cut -d ' ' -f 1) umask 077 printf 'appendonly yes\nuser default reset on #%s ~* &* +@all\n' "$$password_hash" > /tmp/payroll-redis.conf exec redis-server /tmp/payroll-redis.conf init: true volumes: - redis-data:/data healthcheck: test: ["CMD-SHELL", "REDISCLI_AUTH=\"$$REDIS_PASSWORD\" redis-cli --no-auth-warning ping | grep -qx PONG"] interval: 10s timeout: 5s retries: 12 logging: *bounded-logging api: <<: *app volumes: - uploads:/app/backend/uploads - api-logs:/app/backend/logs healthcheck: # /api/health 的 HTTP 200 不代表依赖正常,需要检查 data 三项。 test: ["CMD", "python", "-c", "import json, urllib.request; d=json.load(urllib.request.urlopen('http://127.0.0.1:8000/api/health', timeout=5))['data']; raise SystemExit(0 if all(d.get(k) is True for k in ('app','db','redis')) else 1)"] interval: 30s timeout: 10s retries: 3 start_period: 30s supervisor: <<: *app command: ["python", "-m", "app.cli", "tplus-supervisor"] network_mode: "service:api" volumes: - uploads:/app/backend/uploads depends_on: api: condition: service_healthy web: image: ys-payroll-web:${PAYROLL_VERSION:?请填写本次发布版本} build: context: .. dockerfile: frontend/Dockerfile restart: unless-stopped ports: - "127.0.0.1:${PAYROLL_HTTP_PORT:-8080}:80" depends_on: api: condition: service_healthy logging: *bounded-logging migrate: <<: *app profiles: ["tools"] restart: "no" command: ["alembic", "upgrade", "head"] volumes: mysql-data: redis-data: uploads: api-logs: