224 lines
6.9 KiB
YAML
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:
|