Files

224 lines
6.9 KiB
YAML

name: lehuo-production
x-api-environment: &api-environment
APP_ENV: production
API_PORT: '3000'
DATABASE_URL: ${DATABASE_URL:?DATABASE_URL is required}
REDIS_URL: ${REDIS_URL:?REDIS_URL is required}
SESSION_SECRET: ${SESSION_SECRET:?SESSION_SECRET is required}
SESSION_TOKEN_PEPPER: ${SESSION_TOKEN_PEPPER:?SESSION_TOKEN_PEPPER is required}
AUDIT_IP_HASH_KEY: ${AUDIT_IP_HASH_KEY:?AUDIT_IP_HASH_KEY is required}
FIELD_ENCRYPTION_KEY: ${FIELD_ENCRYPTION_KEY:?FIELD_ENCRYPTION_KEY is required}
ADMIN_WEB_ORIGIN: ${ADMIN_WEB_ORIGIN:?ADMIN_WEB_ORIGIN is required}
ADMIN_COOKIE_SECURE: 'true'
AUTH_SESSION_TTL_SECONDS: ${AUTH_SESSION_TTL_SECONDS:-28800}
WECHAT_SESSION_TTL_SECONDS: ${WECHAT_SESSION_TTL_SECONDS:-7200}
WECHAT_APP_ID: ${WECHAT_APP_ID:-}
WECHAT_APP_SECRET: ${WECHAT_APP_SECRET:-}
WECHAT_TEMPLATE_APPLICATION_SUBMITTED: ${WECHAT_TEMPLATE_APPLICATION_SUBMITTED:-}
WECHAT_TEMPLATE_APPLICATION_ACCEPTED: ${WECHAT_TEMPLATE_APPLICATION_ACCEPTED:-}
WECHAT_TEMPLATE_SERVICE_ARRANGED: ${WECHAT_TEMPLATE_SERVICE_ARRANGED:-}
WECHAT_TEMPLATE_SERVICE_COMPLETED: ${WECHAT_TEMPLATE_SERVICE_COMPLETED:-}
WECHAT_TEMPLATE_FOLLOW_UP_PENDING: ${WECHAT_TEMPLATE_FOLLOW_UP_PENDING:-}
WECHAT_TEMPLATE_ACTIVITY_REGISTRATION_RESULT: ${WECHAT_TEMPLATE_ACTIVITY_REGISTRATION_RESULT:-}
NOTIFICATION_WORKER_ENABLED: ${NOTIFICATION_WORKER_ENABLED:-false}
OBJECT_STORAGE_ENDPOINT: ${OBJECT_STORAGE_ENDPOINT:?OBJECT_STORAGE_ENDPOINT is required}
OBJECT_STORAGE_REGION: ${OBJECT_STORAGE_REGION:-auto}
OBJECT_STORAGE_BUCKET: ${OBJECT_STORAGE_BUCKET:?OBJECT_STORAGE_BUCKET is required}
OBJECT_STORAGE_ACCESS_KEY: ${OBJECT_STORAGE_ACCESS_KEY:?OBJECT_STORAGE_ACCESS_KEY is required}
OBJECT_STORAGE_SECRET_KEY: ${OBJECT_STORAGE_SECRET_KEY:?OBJECT_STORAGE_SECRET_KEY is required}
OBJECT_STORAGE_FORCE_PATH_STYLE: 'true'
services:
mysql:
image: mysql:8.4.8
restart: unless-stopped
command:
- --character-set-server=utf8mb4
- --collation-server=utf8mb4_0900_ai_ci
environment:
MYSQL_DATABASE: ${MYSQL_DATABASE:?MYSQL_DATABASE is required}
MYSQL_USER: ${MYSQL_USER:?MYSQL_USER is required}
MYSQL_PASSWORD: ${MYSQL_PASSWORD:?MYSQL_PASSWORD is required}
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:?MYSQL_ROOT_PASSWORD is required}
TZ: Asia/Shanghai
volumes:
- mysql-data:/var/lib/mysql
networks:
- backend
healthcheck:
test:
- CMD-SHELL
- MYSQL_PWD=$$MYSQL_ROOT_PASSWORD mysqladmin ping -h 127.0.0.1 -u root --silent
interval: 10s
timeout: 5s
retries: 12
start_period: 40s
security_opt:
- no-new-privileges:true
redis:
image: redis:7.4-alpine
restart: unless-stopped
command:
- redis-server
- --appendonly
- 'yes'
- --requirepass
- ${REDIS_PASSWORD:?REDIS_PASSWORD is required}
volumes:
- redis-data:/data
networks:
- backend
healthcheck:
test: [CMD-SHELL, 'redis-cli -a "$$REDIS_PASSWORD" ping | grep -q PONG']
interval: 10s
timeout: 5s
retries: 8
environment:
REDIS_PASSWORD: ${REDIS_PASSWORD:?REDIS_PASSWORD is required}
security_opt:
- no-new-privileges:true
minio:
image: minio/minio:RELEASE.2025-09-07T16-13-09Z
restart: unless-stopped
command: server /data
environment:
MINIO_ROOT_USER: ${MINIO_ROOT_USER:?MINIO_ROOT_USER is required}
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD:?MINIO_ROOT_PASSWORD is required}
MINIO_BROWSER: 'off'
MINIO_SERVER_URL: https://${FILES_HOST:?FILES_HOST is required}
volumes:
- minio-data:/data
networks:
- backend
healthcheck:
test: [CMD, curl, -f, http://localhost:9000/minio/health/live]
interval: 10s
timeout: 5s
retries: 8
security_opt:
- no-new-privileges:true
minio-init:
image: minio/mc:RELEASE.2025-08-13T08-35-41Z
restart: 'no'
entrypoint: [/bin/sh, /bootstrap/init-minio.sh]
environment:
MINIO_ROOT_USER: ${MINIO_ROOT_USER:?MINIO_ROOT_USER is required}
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD:?MINIO_ROOT_PASSWORD is required}
OBJECT_STORAGE_BUCKET: ${OBJECT_STORAGE_BUCKET:?OBJECT_STORAGE_BUCKET is required}
ADMIN_WEB_ORIGIN: ${ADMIN_WEB_ORIGIN:?ADMIN_WEB_ORIGIN is required}
volumes:
- ./minio/init-minio.sh:/bootstrap/init-minio.sh:ro
networks:
- backend
depends_on:
minio:
condition: service_healthy
security_opt:
- no-new-privileges:true
api:
image: lehuo-api:${IMAGE_TAG:?IMAGE_TAG is required}
build:
context: ../..
dockerfile: apps/api/Dockerfile
restart: unless-stopped
environment: *api-environment
extra_hosts:
- ${FILES_HOST:?FILES_HOST is required}:host-gateway
expose:
- '3000'
networks:
- backend
depends_on:
mysql:
condition: service_healthy
redis:
condition: service_healthy
minio-init:
condition: service_completed_successfully
healthcheck:
test:
- CMD
- node
- -e
- fetch('http://127.0.0.1:3000/health/live').then(r=>{if(!r.ok)process.exit(1)}).catch(()=>process.exit(1))
interval: 15s
timeout: 5s
retries: 8
start_period: 30s
read_only: true
tmpfs:
- /tmp:size=64m,noexec,nosuid
cap_drop:
- ALL
security_opt:
- no-new-privileges:true
migrate:
image: lehuo-api:${IMAGE_TAG:?IMAGE_TAG is required}
profiles: [tools]
command:
- ./apps/api/node_modules/.bin/prisma
- migrate
- deploy
- --config
- apps/api/prisma.config.ts
environment:
DATABASE_URL: ${DATABASE_URL:?DATABASE_URL is required}
networks:
- backend
depends_on:
mysql:
condition: service_healthy
read_only: true
tmpfs:
- /tmp:size=64m,noexec,nosuid
cap_drop:
- ALL
security_opt:
- no-new-privileges:true
web:
image: lehuo-admin:${IMAGE_TAG:?IMAGE_TAG is required}
build:
context: ../..
dockerfile: apps/admin-web/Dockerfile
args:
VITE_API_BASE_URL: https://${PUBLIC_HOST:?PUBLIC_HOST is required}
restart: unless-stopped
environment:
PUBLIC_HOST: ${PUBLIC_HOST:?PUBLIC_HOST is required}
FILES_HOST: ${FILES_HOST:?FILES_HOST is required}
NGINX_ENVSUBST_FILTER: ^(PUBLIC_HOST|FILES_HOST)$$
ports:
- '80:80'
- '443:443'
volumes:
- ${TLS_CERT_PATH:?TLS_CERT_PATH is required}:/etc/nginx/tls/fullchain.pem:ro
- ${TLS_KEY_PATH:?TLS_KEY_PATH is required}:/etc/nginx/tls/privkey.pem:ro
networks:
- backend
- edge
depends_on:
api:
condition: service_healthy
read_only: true
tmpfs:
- /var/cache/nginx:size=32m,noexec,nosuid
- /var/run:size=4m,noexec,nosuid
security_opt:
- no-new-privileges:true
networks:
backend:
edge:
volumes:
mysql-data:
minio-data:
redis-data: