Files
backend/docker-compose.prod.yml
T
34047007@qq.com c55d096769
CI / backend (push) Waiting to run
CI / frontend (push) Waiting to run
feat: deploy/deploy.sh 自动化部署体系 — 双tag镜像/快照/迁移门控/回滚,退役根deploy.sh
- compose 四服务补 image: ${BACKEND_TAG}/${FRONTEND_TAG} 插值(换 tag 回滚前提)
- deploy/deploy.sh:工作区校验→PREV_SHA→pull→predeploy 快照(-Fc)→destructive 判定→
  build+双tag→.pending-deploy→迁移退出码门控→up -d --no-deps→健康轮询→versions.log→
  镜像治理;支持 --dry-run / flock / 基础设施健康门 / 多head预检
- deploy/rollback.sh:双入口(手动读 versions.log 跳 ROLLBACK 行 / --auto 读
  .pending-deploy);destructive 走 pg_restore --clean;双 tag 原子切换
- deploy/hotfix.sh:紧急单文件 cp + 强制收口提醒
- .gitignore 补 .pending-deploy/versions.log(运行时状态)
- 退役根 deploy.sh + deploy.env.example(SSH 旧模型,被 deploy/deploy.sh 取代)
2026-08-10 16:30:43 +08:00

317 lines
8.2 KiB
YAML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# SciLit 生产环境 Docker Compose
#
# 使用方式:
# cp .env.example .env # 填入实际值
# docker compose -f docker-compose.prod.yml --env-file .env up -d
#
# HTTPS 终止建议使用上游反向代理(Caddy / Nginx / ALB),
# 前端 Nginx 容器本身仅提供 HTTP 服务。
version: "3.9"
networks:
scilit:
driver: bridge
services:
postgres:
image: pgvector/pgvector:pg16
networks:
- scilit
environment:
POSTGRES_DB: scilit
POSTGRES_USER: scilit
POSTGRES_PASSWORD: ${PG_PASSWORD}
ports: []
volumes:
- pgdata:/var/lib/postgresql/data
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "pg_isready -U scilit"]
interval: 10s
timeout: 5s
retries: 5
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
redis:
image: redis:7-alpine
networks:
- scilit
command: redis-server --requirepass ${REDIS_PASSWORD}
ports: []
volumes:
- redisdata:/data
restart: unless-stopped
healthcheck:
test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD}", "ping"]
interval: 10s
timeout: 5s
retries: 5
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
elasticsearch:
image: elasticsearch:8.11.0
networks:
- scilit
environment:
discovery.type: single-node
xpack.security.enabled: "false"
"ES_JAVA_OPTS": "-Xms1g -Xmx1g"
bootstrap.memory_lock: "true"
ports: []
volumes:
- esdata:/usr/share/elasticsearch/data
restart: unless-stopped
ulimits:
memlock:
soft: -1
hard: -1
healthcheck:
test: ["CMD-SHELL", "curl -s http://localhost:9200/_cluster/health | grep -qE 'green|yellow'"]
interval: 15s
timeout: 10s
retries: 5
start_period: 30s
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
minio:
image: minio/minio:latest
command: server /data --console-address ":9001"
networks:
- scilit
environment:
MINIO_ROOT_USER: ${MINIO_ROOT_USER:-minioadmin}
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD:-minioadmin}
ports: []
volumes:
- miniodata:/data
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "curl -sf http://localhost:9000/minio/health/live || exit 1"]
interval: 15s
timeout: 5s
retries: 5
start_period: 10s
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
migrate:
image: scilit/backend:${BACKEND_TAG:-latest}
build: ./backend
networks:
- scilit
command: alembic -c alembic/alembic.ini upgrade head
environment:
DATABASE_URL: postgresql+asyncpg://scilit:${PG_PASSWORD}@postgres:5432/scilit
JWT_SECRET: ${JWT_SECRET}
SPECIALTY: ${SPECIALTY:-oncology}
DEBUG: "false"
depends_on:
postgres:
condition: service_healthy
restart: "no"
backend:
image: scilit/backend:${BACKEND_TAG:-latest}
build: ./backend
networks:
- scilit
# ENTRYPOINT 自动执行迁移,CMD 启动 uvicorn4 workers + proxy-headers
# 无需额外 command
environment:
DATABASE_URL: postgresql+asyncpg://scilit:${PG_PASSWORD}@postgres:5432/scilit
REDIS_URL: redis://default:${REDIS_PASSWORD}@redis:6379
ES_URL: http://elasticsearch:9200
S3_ENDPOINT: ${S3_ENDPOINT}
S3_ACCESS_KEY: ${S3_ACCESS_KEY}
S3_SECRET_KEY: ${S3_SECRET_KEY}
S3_BUCKET: ${S3_BUCKET:-scilit-files}
COS_SECRET_ID: ${COS_SECRET_ID:-}
COS_SECRET_KEY: ${COS_SECRET_KEY:-}
COS_REGION: ${COS_REGION:-ap-guangzhou}
COS_BUCKET: ${COS_BUCKET:-scilit-files}
JWT_SECRET: ${JWT_SECRET}
SPECIALTY: ${SPECIALTY:-oncology}
DEBUG: "false"
SMTP_HOST: ${SMTP_HOST}
SMTP_PORT: ${SMTP_PORT:-587}
SMTP_USER: ${SMTP_USER}
SMTP_PASSWORD: ${SMTP_PASSWORD}
SMTP_FROM: ${SMTP_FROM}
SENTRY_DSN: ${SENTRY_DSN}
AI_API_KEY: ${AI_API_KEY}
AI_BASE_URL: ${AI_BASE_URL}
AI_MODEL: ${AI_MODEL}
PUBMED_API_KEY: ${PUBMED_API_KEY}
CORS_ORIGINS: ${CORS_ORIGINS}
PUBLIC_BASE_URL: ${PUBLIC_BASE_URL}
ports:
- "127.0.0.1:8000:8000"
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
elasticsearch:
condition: service_started
minio:
condition: service_healthy
migrate:
condition: service_completed_successfully
healthcheck:
test: ["CMD-SHELL", "curl -sf http://localhost:8000/health || exit 1"]
interval: 15s
timeout: 5s
retries: 3
start_period: 20s
restart: unless-stopped
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
worker:
image: scilit/backend:${BACKEND_TAG:-latest}
build: ./backend
networks:
- scilit
# 通过 CMD 覆写默认的 uvicorn 启动,改为 ARQ worker(异步任务消费)
# 迁移由独立的 migrate 服务完成(depends_on 确保顺序)
command: arq app.tasks.worker.WorkerSettings
healthcheck:
test: ["CMD-SHELL", "grep -q arq /proc/1/cmdline"]
interval: 30s
timeout: 5s
start_period: 30s
retries: 3
environment:
DATABASE_URL: postgresql+asyncpg://scilit:${PG_PASSWORD}@postgres:5432/scilit
REDIS_URL: redis://default:${REDIS_PASSWORD}@redis:6379
ES_URL: http://elasticsearch:9200
S3_ENDPOINT: ${S3_ENDPOINT}
S3_ACCESS_KEY: ${S3_ACCESS_KEY}
S3_SECRET_KEY: ${S3_SECRET_KEY}
S3_BUCKET: ${S3_BUCKET:-scilit-files}
COS_SECRET_ID: ${COS_SECRET_ID:-}
COS_SECRET_KEY: ${COS_SECRET_KEY:-}
COS_REGION: ${COS_REGION:-ap-guangzhou}
COS_BUCKET: ${COS_BUCKET:-scilit-files}
JWT_SECRET: ${JWT_SECRET}
SPECIALTY: ${SPECIALTY:-oncology}
DEBUG: "false"
SMTP_HOST: ${SMTP_HOST}
SMTP_PORT: ${SMTP_PORT:-587}
SMTP_USER: ${SMTP_USER}
SMTP_PASSWORD: ${SMTP_PASSWORD}
SMTP_FROM: ${SMTP_FROM}
AI_API_KEY: ${AI_API_KEY}
AI_BASE_URL: ${AI_BASE_URL}
AI_MODEL: ${AI_MODEL}
PUBMED_API_KEY: ${PUBMED_API_KEY}
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
minio:
condition: service_healthy
migrate:
condition: service_completed_successfully
restart: unless-stopped
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
frontend:
image: scilit/frontend:${FRONTEND_TAG:-latest}
build:
context: ./frontend
dockerfile: Dockerfile.prod
networks:
- scilit
environment:
VITE_API_BASE: /api/v1
ports:
- "127.0.0.1:8080:80"
volumes:
- /var/log/scilit/nginx:/var/log/nginx
depends_on:
backend:
condition: service_started
restart: unless-stopped
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
gitea:
image: gitea/gitea:1.27.1-rootless
restart: unless-stopped
networks:
- scilit
volumes:
- gitea_data:/var/lib/gitea
environment:
GITEA__database__DB_TYPE: postgres
GITEA__database__HOST: postgres:5432
GITEA__database__NAME: gitea
GITEA__database__USER: gitea
GITEA__database__PASSWD: gitea_pass_2026
GITEA__server__DOMAIN: gitea.oncolit.gonsun.com
GITEA__server__HTTP_PORT: 3000
GITEA__server__ROOT_URL: https://gitea.oncolit.gonsun.com/
GITEA__server__START_SSH_SERVER: true
GITEA__server__SSH_DOMAIN: gitea.oncolit.gonsun.com
GITEA__server__SSH_PORT: 2222
GITEA__server__SSH_LISTEN_PORT: 22
ports:
- 3000:3000
- 2222:22
caddy:
image: caddy:2-alpine
networks:
- scilit
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- caddy_data:/data
- caddy_config:/config
restart: unless-stopped
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
volumes:
caddy_data:
caddy_config:
pgdata:
redisdata:
esdata:
miniodata:
gitea_data: