Files
FastapiAdmin/docker/docker-compose.yaml
T
zhangtao ce9401196a chore(deploy): 成品部署链路、依赖分组与运维基建
部署方式:服务器不再接收源码、不执行构建,改为本地构建成品后交付
- deploy-artifacts.sh:构建→备份→校验→原子替换,支持回滚、镜像/备份轮转、
  mysql 与 postgres 双变体镜像;后端以 docker save|ssh|docker load 交付
- 依赖分组:requirements/{base,storage}.txt + db/{mysql,postgres,sqlite}.txt,
  镜像按所选数据库只装对应驱动(MySQL 变体 565MB → 521MB)

编排与运行时加固
- compose:MySQL/Redis/backend 端口仅绑 127.0.0.1;密钥(SECRET_KEY、
  DATA_ENCRYPTION_KEY、DATA_ENCRYPTION_OLD_KEYS)、PROD_CORS_ORIGINS、
  FORWARDED_ALLOW_IPS、DEMO_ENABLE 一律由 docker/.env 注入
- Dockerfile:对齐依赖分组、以非 root(uid=1000) 运行;.dockerignore 排除全部真实 .env.*
- nginx:静态资源长缓存、隐藏文件拒绝、接口文档仅内网可访问、放行 ACME http-01 校验路径

工程基建
- 新增 CI:backend(uv sync + ruff + pytest)与 frontend(web type-check+vitest、app type-check)
- 新增 DEPLOYMENT.md 运维手册(部署/回滚/密钥轮换/每日巡检/证书/排查)
2026-10-04 04:26:03 +08:00

232 lines
7.4 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.
# ============================================
# FastapiAdmin Docker Compose 配置文件
# ============================================
# 使用方式:
# 开发环境: docker compose --env-file .env up -d
# 生产环境: docker compose --env-file .env -f docker-compose.yaml up -d
# ============================================
services:
# ==================== 数据库服务 ====================
mysql:
container_name: mysql
image: mysql:8.0
restart: unless-stopped
platform: linux/amd64
environment:
TZ: "Asia/Shanghai"
MYSQL_ROOT_PASSWORD: "${MYSQL_ROOT_PASSWORD:?错误: 请设置 MYSQL_ROOT_PASSWORD 环境变量}"
MYSQL_DATABASE: "${MYSQL_DATABASE:-fastapiadmin}"
MYSQL_USER: "${MYSQL_USER:-fastapiadmin}"
MYSQL_PASSWORD: "${MYSQL_PASSWORD:?错误: 请设置 MYSQL_PASSWORD 环境变量}"
ports:
- "127.0.0.1:${MYSQL_PORT:-3306}:3306"
volumes:
- mysql_data:/var/lib/mysql
- /etc/localtime:/etc/localtime:ro
# 首次启动时自动执行 init SQL 脚本
- ./mysql/init:/docker-entrypoint-initdb.d:ro
networks:
- app_network
healthcheck:
test:
["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p${MYSQL_ROOT_PASSWORD}"]
interval: 10s
timeout: 10s
retries: 10
start_period: 30s
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
deploy:
resources:
limits:
memory: 1g
reservations:
memory: 256m
# ==================== Redis 服务 ====================
redis:
container_name: redis
image: redis:7-alpine
restart: unless-stopped
platform: linux/amd64
environment:
TZ: "Asia/Shanghai"
ports:
- "127.0.0.1:${REDIS_PORT:-6379}:6379"
volumes:
- redis_data:/data
# 挂载自定义 redis.conf(若存在则覆盖默认配置)
- ./redis/conf/redis.conf:/usr/local/etc/redis/redis.conf:ro
command: >
redis-server /usr/local/etc/redis/redis.conf
--requirepass ${REDIS_PASSWORD:?错误: 请设置 REDIS_PASSWORD 环境变量}
networks:
- app_network
healthcheck:
test:
["CMD", "redis-cli", "-a", "${REDIS_PASSWORD}", "ping"]
interval: 10s
timeout: 5s
retries: 5
start_period: 10s
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
deploy:
resources:
limits:
memory: 512m
reservations:
memory: 128m
# ==================== 后端服务 ====================
backend:
container_name: backend
build:
context: ../
dockerfile: ./docker/backend/Dockerfile
args:
DEPLOY_ENV: "${DEPLOY_ENV:-prod}"
image: "backend:${BACKEND_IMAGE_TAG:-3.0.0}"
restart: unless-stopped
platform: linux/amd64
environment:
TZ: "Asia/Shanghai"
# 运行环境,对应 backend/env/.env.{dev,prod}
ENVIRONMENT: "${DEPLOY_ENV:-prod}"
# MySQL 连接配置
DATABASE_HOST: "mysql"
DATABASE_PORT: "3306"
DATABASE_USER: "${MYSQL_USER:-fastapiadmin}"
DATABASE_PASSWORD: "${MYSQL_PASSWORD:?错误: 请设置 MYSQL_PASSWORD 环境变量}"
DATABASE_NAME: "${MYSQL_DATABASE:-fastapiadmin}"
# Redis 连接配置
REDIS_HOST: "redis"
REDIS_PORT: "6379"
REDIS_PASSWORD: "${REDIS_PASSWORD}"
REDIS_DB_NAME: "1"
REDIS_ENABLE: "true"
# 日志级别
LOGGER_LEVEL: "${LOGGER_LEVEL:-INFO}"
# 信任反向代理(nginx)传来的 X-Forwarded-Proto/For:否则 prod 的
# HTTPSRedirectMiddleware 会把 https 请求误判为 http 造成 301 循环。
# 只信任应用网络子网(默认 10.255.178.0/24),不要用 "*"——否则客户端可伪造
# X-Forwarded-For 绕过登录限流与来源 IP 记录。
FORWARDED_ALLOW_IPS: "${FORWARDED_ALLOW_IPS:-10.255.178.0/24}"
# 令牌签名密钥与数据加密密钥(必须显式提供,禁止使用源码默认值)
SECRET_KEY: "${SECRET_KEY:?错误: 请设置 SECRET_KEY 环境变量(openssl rand -hex 32)}"
DATA_ENCRYPTION_KEY: "${DATA_ENCRYPTION_KEY:-}"
# 轮换前的旧主密钥(逗号分隔),仅用于解密历史密文
DATA_ENCRYPTION_OLD_KEYS: "${DATA_ENCRYPTION_OLD_KEYS:-}"
# 生产跨域白名单(逗号分隔)。未配置时应用不再回落通配
PROD_CORS_ORIGINS: "${PROD_CORS_ORIGINS:-}"
# 演示模式:为 true 时禁止写操作。当前线上为 true,如需可写请设为 false
DEMO_ENABLE: "${DEMO_ENABLE:-true}"
ports:
# 仅绑定本机:数据面不对公网开放,容器之间经 app_network 互联
- "127.0.0.1:${BACKEND_PORT:-8001}:8001"
volumes:
# ── 开发热更新(可选)─────────────────────
# 取消注释将宿主机代码覆盖容器内代码。生产环境保持注释,
# 使用镜像内自带的代码(Dockerfile 已 COPY backend)。
# ─────────────────────────────────────────
# - ../backend:/home
# 本地存储源根目录持久化(种子节点 host=static/upload):
# 必须挂载,否则每次部署重建容器会丢失用户上传的文件
- ./backend/static/upload:/home/static/upload
depends_on:
mysql:
condition: service_healthy
redis:
condition: service_healthy
networks:
- app_network
healthcheck:
test:
[
"CMD-SHELL",
"python -c 'import urllib.request; req = urllib.request.Request(\"http://localhost:8001/api/v1/monitor/health/check\", headers={\"X-Forwarded-Proto\": \"https\"}); urllib.request.urlopen(req, timeout=5)' || exit 1",
]
interval: 15s
timeout: 10s
retries: 5
start_period: 40s
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
deploy:
resources:
limits:
memory: 1g
cpus: "1.0"
reservations:
memory: 256m
# ==================== Nginx 服务 ====================
nginx:
container_name: nginx
image: nginx:1.25-alpine
restart: unless-stopped
platform: linux/amd64
environment:
TZ: "Asia/Shanghai"
ports:
- "${HTTP_PORT:-80}:80"
- "${HTTPS_PORT:-443}:443"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
- ./nginx/web:/usr/share/nginx/html/web:ro
- ./nginx/ssl:/etc/nginx/ssl:ro
- ./nginx/app:/usr/share/nginx/html/app:ro
- ./nginx/docs:/usr/share/nginx/html/docs:ro
depends_on:
backend:
condition: service_started
networks:
- app_network
healthcheck:
test: ["CMD", "nginx", "-t"]
interval: 30s
timeout: 5s
retries: 3
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
deploy:
resources:
limits:
memory: 256m
cpus: "0.5"
reservations:
memory: 64m
# ==================== 持久化卷 ====================
volumes:
mysql_data:
driver: local
driver_opts:
type: none
device: ./mysql/data
o: bind
redis_data:
driver: local
driver_opts:
type: none
device: ./redis/data
o: bind
# ==================== 网络 ====================
networks:
app_network:
driver: bridge