feat: 添加 Docker 容器化部署支持
支持两种部署模式,兼容新建 MySQL 和现有 MySQL: - Dockerfile:Python 3.12-slim 两阶段构建,非 root 运行 - docker-compose.yml:全栈模式(含 MySQL 8.0 + 可选 Nginx) - docker-compose.external-db.yml:接入现有 MySQL 模式 - docker/entrypoint.sh:自动等待 DB 就绪 → 初始化表 → 启动 Gunicorn - docker/nginx.conf:反向代理 + 静态文件直出 + 安全响应头 - .env.docker.example / .env.external-db.example:各模式配置示例 - .gitattributes:确保 entrypoint.sh 在 Windows 上保持 LF 换行 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
44
.dockerignore
Normal file
44
.dockerignore
Normal file
@@ -0,0 +1,44 @@
|
||||
# ── Python 缓存 ──────────────────────────────────────────────
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*.pyo
|
||||
*.pyd
|
||||
.Python
|
||||
|
||||
# ── 虚拟环境 ─────────────────────────────────────────────────
|
||||
.venv/
|
||||
venv/
|
||||
env/
|
||||
ENV/
|
||||
|
||||
# ── 环境变量(不打包进镜像!)────────────────────────────────
|
||||
.env
|
||||
.env.docker
|
||||
.env.external-db
|
||||
|
||||
# ── 上传文件(挂载卷提供,不打包)───────────────────────────
|
||||
app/static/uploads/
|
||||
|
||||
# ── 数据库迁移临时文件 ───────────────────────────────────────
|
||||
migrations/
|
||||
|
||||
# ── 版本控制 ─────────────────────────────────────────────────
|
||||
.git/
|
||||
.gitignore
|
||||
|
||||
# ── IDE/编辑器 ───────────────────────────────────────────────
|
||||
.idea/
|
||||
.vscode/
|
||||
*.swp
|
||||
*.swo
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# ── 测试/文档 ────────────────────────────────────────────────
|
||||
tests/
|
||||
*.md
|
||||
docs/
|
||||
|
||||
# ── 日志 ─────────────────────────────────────────────────────
|
||||
*.log
|
||||
logs/
|
||||
40
.env.docker.example
Normal file
40
.env.docker.example
Normal file
@@ -0,0 +1,40 @@
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# 模式一:新建 MySQL 全栈部署
|
||||
# 使用方式:cp .env.docker.example .env.docker
|
||||
# 编辑后执行:docker compose --env-file .env.docker up -d
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
# ── 应用安全 ────────────────────────────────────────────────────
|
||||
# 必须修改!使用随机长字符串
|
||||
SECRET_KEY=change-this-to-a-very-long-random-string-in-production
|
||||
|
||||
# ── MySQL(新建数据库配置)──────────────────────────────────────
|
||||
MYSQL_ROOT_PASSWORD=StrongRootPass@2026
|
||||
MYSQL_DATABASE=resource_library
|
||||
MYSQL_USER=resource_library
|
||||
MYSQL_PASSWORD=StrongUserPass@2026
|
||||
|
||||
# 是否对外暴露 MySQL 端口(生产环境建议保持 127.0.0.1 限制)
|
||||
# 仅本机访问(推荐): MYSQL_EXPOSE_PORT=127.0.0.1:3306
|
||||
# 所有网络接口(开发调试): MYSQL_EXPOSE_PORT=3306
|
||||
MYSQL_EXPOSE_PORT=127.0.0.1:3306
|
||||
|
||||
# ── 管理员账号(首次启动自动创建)────────────────────────────────
|
||||
ADMIN_USERNAME=admin
|
||||
ADMIN_PASSWORD=Admin@123456
|
||||
ADMIN_EMAIL=admin@example.com
|
||||
|
||||
# ── 应用配置 ────────────────────────────────────────────────────
|
||||
FLASK_ENV=production
|
||||
APP_PORT=5000
|
||||
MAX_UPLOAD_SIZE_MB=500
|
||||
LOG_LEVEL=info
|
||||
|
||||
# ── Gunicorn ────────────────────────────────────────────────────
|
||||
# 建议:CPU 核心数 × 2 + 1
|
||||
GUNICORN_WORKERS=4
|
||||
GUNICORN_TIMEOUT=120
|
||||
|
||||
# ── Nginx(--profile nginx 时生效)──────────────────────────────
|
||||
NGINX_HTTP_PORT=80
|
||||
NGINX_HTTPS_PORT=443
|
||||
45
.env.external-db.example
Normal file
45
.env.external-db.example
Normal file
@@ -0,0 +1,45 @@
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# 模式二:使用现有 MySQL(外部数据库)
|
||||
# 使用方式:cp .env.external-db.example .env.external-db
|
||||
# 编辑后执行:
|
||||
# docker compose -f docker-compose.external-db.yml \
|
||||
# --env-file .env.external-db up -d
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
# ── 应用安全 ────────────────────────────────────────────────────
|
||||
SECRET_KEY=change-this-to-a-very-long-random-string-in-production
|
||||
|
||||
# ── 外部数据库连接(修改为实际连接信息)─────────────────────────
|
||||
# 格式:mysql+pymysql://用户名:密码@主机:端口/数据库名
|
||||
#
|
||||
# 示例 1 - 本项目现有数据库:
|
||||
# DATABASE_URL=mysql+pymysql://resource_library:BWRVzzCwzuuP_1Hj@pma.hty1024.com:31000/resource_library
|
||||
#
|
||||
# 示例 2 - 宿主机 MySQL(容器访问宿主机使用 host.docker.internal):
|
||||
# DATABASE_URL=mysql+pymysql://resource_library:password@host.docker.internal:3306/resource_library
|
||||
#
|
||||
# 示例 3 - 标准端口的云数据库:
|
||||
# DATABASE_URL=mysql+pymysql://user:password@your-rds.amazonaws.com:3306/resource_library
|
||||
DATABASE_URL=mysql+pymysql://resource_library:your_password@your_db_host:3306/resource_library
|
||||
|
||||
# 等待数据库就绪的超时时间(秒)
|
||||
DB_WAIT_SECONDS=30
|
||||
|
||||
# ── 管理员账号(首次启动自动创建,已存在则跳过)────────────────
|
||||
ADMIN_USERNAME=admin
|
||||
ADMIN_PASSWORD=Admin@123456
|
||||
ADMIN_EMAIL=admin@example.com
|
||||
|
||||
# ── 应用配置 ────────────────────────────────────────────────────
|
||||
FLASK_ENV=production
|
||||
APP_PORT=5000
|
||||
MAX_UPLOAD_SIZE_MB=500
|
||||
LOG_LEVEL=info
|
||||
|
||||
# ── Gunicorn ────────────────────────────────────────────────────
|
||||
GUNICORN_WORKERS=4
|
||||
GUNICORN_TIMEOUT=120
|
||||
|
||||
# ── Nginx(--profile nginx 时生效)──────────────────────────────
|
||||
NGINX_HTTP_PORT=80
|
||||
NGINX_HTTPS_PORT=443
|
||||
14
.gitattributes
vendored
Normal file
14
.gitattributes
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
# 强制 Shell 脚本使用 LF(在 Windows 上构建 Linux 镜像时必须)
|
||||
docker/entrypoint.sh text eol=lf
|
||||
*.sh text eol=lf
|
||||
|
||||
# 普通文件使用系统默认换行
|
||||
*.py text
|
||||
*.html text
|
||||
*.css text
|
||||
*.js text
|
||||
*.md text
|
||||
*.yml text
|
||||
*.yaml text
|
||||
*.txt text
|
||||
*.json text
|
||||
67
Dockerfile
Normal file
67
Dockerfile
Normal file
@@ -0,0 +1,67 @@
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# 个人资料库 — Dockerfile
|
||||
# 构建方式:docker build -t resource-library .
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
# ── 阶段 1:依赖构建(独立层,仅在 requirements.txt 变更时重建)──
|
||||
FROM python:3.12-slim AS builder
|
||||
|
||||
WORKDIR /build
|
||||
|
||||
# 安装编译依赖(cryptography 等需要 gcc)
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
gcc libffi-dev libssl-dev default-libmysqlclient-dev pkg-config \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
COPY requirements.txt .
|
||||
|
||||
# 编译到 wheel 缓存目录,下一阶段直接 pip install --no-index
|
||||
RUN pip wheel --no-cache-dir --wheel-dir /build/wheels -r requirements.txt
|
||||
|
||||
|
||||
# ── 阶段 2:运行镜像(精简,不含编译工具)──────────────────────
|
||||
FROM python:3.12-slim AS runtime
|
||||
|
||||
# 运行时系统依赖(libmagic 用于文件类型识别)
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
libmagic1 curl \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# 创建非 root 运行用户
|
||||
RUN groupadd -r appuser && useradd -r -g appuser appuser
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# 从 builder 安装预编译 wheels(离线,无需网络)
|
||||
COPY --from=builder /build/wheels /tmp/wheels
|
||||
COPY requirements.txt .
|
||||
RUN pip install --no-cache-dir --no-index --find-links /tmp/wheels -r requirements.txt \
|
||||
&& rm -rf /tmp/wheels
|
||||
|
||||
# 单独安装 gunicorn(WSGI 服务器)
|
||||
RUN pip install --no-cache-dir gunicorn==23.0.0
|
||||
|
||||
# 复制应用代码
|
||||
COPY . .
|
||||
|
||||
# 创建上传目录并设置权限
|
||||
RUN mkdir -p app/static/uploads/{text,image,audio,video,temp} \
|
||||
&& chown -R appuser:appuser /app
|
||||
|
||||
# 复制并授权启动脚本
|
||||
COPY docker/entrypoint.sh /entrypoint.sh
|
||||
RUN chmod +x /entrypoint.sh
|
||||
|
||||
# 切换到非 root 用户
|
||||
USER appuser
|
||||
|
||||
# 声明上传目录为卷
|
||||
VOLUME ["/app/app/static/uploads"]
|
||||
|
||||
EXPOSE 5000
|
||||
|
||||
# 健康检查
|
||||
HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
|
||||
CMD curl -f http://localhost:5000/auth/login || exit 1
|
||||
|
||||
ENTRYPOINT ["/entrypoint.sh"]
|
||||
89
docker-compose.external-db.yml
Normal file
89
docker-compose.external-db.yml
Normal file
@@ -0,0 +1,89 @@
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# docker-compose.external-db.yml — 模式二:使用现有 MySQL
|
||||
#
|
||||
# 适用场景:
|
||||
# - 已有云数据库(如 RDS、本项目使用的 pma.hty1024.com:31000)
|
||||
# - 不想在容器内运行 MySQL
|
||||
#
|
||||
# 用法:
|
||||
# cp .env.external-db.example .env.external-db
|
||||
# # 编辑 .env.external-db 填写实际数据库连接信息
|
||||
# docker compose -f docker-compose.external-db.yml \
|
||||
# --env-file .env.external-db up -d
|
||||
#
|
||||
# 包含服务:
|
||||
# app — Flask + Gunicorn
|
||||
# nginx — Nginx 反向代理(可选,使用 --profile nginx 启用)
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
services:
|
||||
|
||||
# ── Flask 应用 ──────────────────────────────────────────────
|
||||
app:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
image: resource-library:latest
|
||||
container_name: resource_library_app
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
FLASK_ENV: ${FLASK_ENV:-production}
|
||||
SECRET_KEY: ${SECRET_KEY}
|
||||
DATABASE_URL: ${DATABASE_URL} # 完整连接串,见 .env.external-db.example
|
||||
ADMIN_USERNAME: ${ADMIN_USERNAME:-admin}
|
||||
ADMIN_PASSWORD: ${ADMIN_PASSWORD:-Admin@123456}
|
||||
ADMIN_EMAIL: ${ADMIN_EMAIL:-admin@example.com}
|
||||
GUNICORN_WORKERS: ${GUNICORN_WORKERS:-4}
|
||||
GUNICORN_TIMEOUT: ${GUNICORN_TIMEOUT:-120}
|
||||
MAX_UPLOAD_SIZE_MB: ${MAX_UPLOAD_SIZE_MB:-500}
|
||||
LOG_LEVEL: ${LOG_LEVEL:-info}
|
||||
# 外部数据库等待超时(秒)
|
||||
DB_WAIT_SECONDS: ${DB_WAIT_SECONDS:-30}
|
||||
volumes:
|
||||
- uploads_data:/app/app/static/uploads
|
||||
ports:
|
||||
- "${APP_PORT:-5000}:5000"
|
||||
extra_hosts:
|
||||
# 将外部数据库主机名加入容器 hosts(若使用域名访问外部 DB)
|
||||
# 格式:- "db-host:实际IP"
|
||||
# 若主机名可正常解析则不需要此配置
|
||||
- "host-gateway:host-gateway"
|
||||
networks:
|
||||
- frontend
|
||||
logging:
|
||||
driver: json-file
|
||||
options:
|
||||
max-size: "20m"
|
||||
max-file: "5"
|
||||
|
||||
# ── Nginx 反向代理(可选)──────────────────────────────────
|
||||
nginx:
|
||||
image: nginx:1.27-alpine
|
||||
container_name: resource_library_nginx
|
||||
restart: unless-stopped
|
||||
profiles: ["nginx"]
|
||||
depends_on:
|
||||
- app
|
||||
ports:
|
||||
- "${NGINX_HTTP_PORT:-80}:80"
|
||||
- "${NGINX_HTTPS_PORT:-443}:443"
|
||||
volumes:
|
||||
- ./docker/nginx.conf:/etc/nginx/conf.d/default.conf:ro
|
||||
- uploads_data:/app/app/static/uploads:ro
|
||||
networks:
|
||||
- frontend
|
||||
logging:
|
||||
driver: json-file
|
||||
options:
|
||||
max-size: "10m"
|
||||
max-file: "3"
|
||||
|
||||
# ── 持久化卷 ────────────────────────────────────────────────────
|
||||
volumes:
|
||||
uploads_data:
|
||||
name: resource_library_uploads
|
||||
|
||||
# ── 网络 ────────────────────────────────────────────────────────
|
||||
networks:
|
||||
frontend:
|
||||
name: resource_library_frontend
|
||||
118
docker-compose.yml
Normal file
118
docker-compose.yml
Normal file
@@ -0,0 +1,118 @@
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# docker-compose.yml — 模式一:新建 MySQL(全栈部署)
|
||||
#
|
||||
# 用法:
|
||||
# cp .env.docker.example .env.docker
|
||||
# # 编辑 .env.docker 修改密码和 SECRET_KEY
|
||||
# docker compose --env-file .env.docker up -d
|
||||
#
|
||||
# 包含服务:
|
||||
# db — MySQL 8.0
|
||||
# app — Flask + Gunicorn
|
||||
# nginx — Nginx 反向代理(可选,使用 --profile nginx 启用)
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
services:
|
||||
|
||||
# ── MySQL 数据库 ────────────────────────────────────────────
|
||||
db:
|
||||
image: mysql:8.0
|
||||
container_name: resource_library_db
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
|
||||
MYSQL_DATABASE: ${MYSQL_DATABASE:-resource_library}
|
||||
MYSQL_USER: ${MYSQL_USER:-resource_library}
|
||||
MYSQL_PASSWORD: ${MYSQL_PASSWORD}
|
||||
volumes:
|
||||
- mysql_data:/var/lib/mysql
|
||||
- ./docker/mysql-init:/docker-entrypoint-initdb.d:ro # 可放自定义初始化 SQL
|
||||
command: >
|
||||
--character-set-server=utf8mb4
|
||||
--collation-server=utf8mb4_unicode_ci
|
||||
--default-authentication-plugin=mysql_native_password
|
||||
--innodb-buffer-pool-size=256M
|
||||
ports:
|
||||
- "${MYSQL_EXPOSE_PORT:-127.0.0.1:3306}:3306" # 默认仅本机可访问
|
||||
healthcheck:
|
||||
test: ["CMD", "mysqladmin", "ping", "-h", "localhost",
|
||||
"-u", "${MYSQL_USER:-resource_library}",
|
||||
"-p${MYSQL_PASSWORD}"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
start_period: 30s
|
||||
networks:
|
||||
- backend
|
||||
|
||||
# ── Flask 应用 ──────────────────────────────────────────────
|
||||
app:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
container_name: resource_library_app
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
FLASK_ENV: ${FLASK_ENV:-production}
|
||||
SECRET_KEY: ${SECRET_KEY}
|
||||
DATABASE_URL: mysql+pymysql://${MYSQL_USER:-resource_library}:${MYSQL_PASSWORD}@db:3306/${MYSQL_DATABASE:-resource_library}
|
||||
ADMIN_USERNAME: ${ADMIN_USERNAME:-admin}
|
||||
ADMIN_PASSWORD: ${ADMIN_PASSWORD:-Admin@123456}
|
||||
ADMIN_EMAIL: ${ADMIN_EMAIL:-admin@example.com}
|
||||
GUNICORN_WORKERS: ${GUNICORN_WORKERS:-4}
|
||||
GUNICORN_TIMEOUT: ${GUNICORN_TIMEOUT:-120}
|
||||
MAX_UPLOAD_SIZE_MB: ${MAX_UPLOAD_SIZE_MB:-500}
|
||||
LOG_LEVEL: ${LOG_LEVEL:-info}
|
||||
volumes:
|
||||
- uploads_data:/app/app/static/uploads
|
||||
ports:
|
||||
- "${APP_PORT:-5000}:5000" # 使用 Nginx 时可去掉此行
|
||||
networks:
|
||||
- backend
|
||||
- frontend
|
||||
logging:
|
||||
driver: json-file
|
||||
options:
|
||||
max-size: "20m"
|
||||
max-file: "5"
|
||||
|
||||
# ── Nginx 反向代理(可选,加 --profile nginx 启用)──────────
|
||||
nginx:
|
||||
image: nginx:1.27-alpine
|
||||
container_name: resource_library_nginx
|
||||
restart: unless-stopped
|
||||
profiles: ["nginx"]
|
||||
depends_on:
|
||||
- app
|
||||
ports:
|
||||
- "${NGINX_HTTP_PORT:-80}:80"
|
||||
- "${NGINX_HTTPS_PORT:-443}:443"
|
||||
volumes:
|
||||
- ./docker/nginx.conf:/etc/nginx/conf.d/default.conf:ro
|
||||
- uploads_data:/app/app/static/uploads:ro # 让 Nginx 直接服务静态文件
|
||||
# - ./docker/ssl:/etc/nginx/ssl:ro # HTTPS 证书目录(取消注释后使用)
|
||||
networks:
|
||||
- frontend
|
||||
logging:
|
||||
driver: json-file
|
||||
options:
|
||||
max-size: "10m"
|
||||
max-file: "3"
|
||||
|
||||
# ── 持久化卷 ────────────────────────────────────────────────────
|
||||
volumes:
|
||||
mysql_data:
|
||||
name: resource_library_mysql
|
||||
uploads_data:
|
||||
name: resource_library_uploads
|
||||
|
||||
# ── 网络 ────────────────────────────────────────────────────────
|
||||
networks:
|
||||
backend:
|
||||
name: resource_library_backend
|
||||
internal: true # db 不暴露给外部网络
|
||||
frontend:
|
||||
name: resource_library_frontend
|
||||
91
docker/entrypoint.sh
Normal file
91
docker/entrypoint.sh
Normal file
@@ -0,0 +1,91 @@
|
||||
#!/bin/sh
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# 容器启动脚本
|
||||
# 职责:等待数据库就绪 → 初始化表结构 → 启动 Gunicorn
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
set -e
|
||||
|
||||
# ── 颜色输出 ──────────────────────────────────────────────────
|
||||
log() { echo "[entrypoint] $*"; }
|
||||
info() { echo "\033[0;36m[entrypoint] $*\033[0m"; }
|
||||
ok() { echo "\033[0;32m[entrypoint] ✓ $*\033[0m"; }
|
||||
warn() { echo "\033[0;33m[entrypoint] ⚠ $*\033[0m"; }
|
||||
err() { echo "\033[0;31m[entrypoint] ✗ $*\033[0m" >&2; }
|
||||
|
||||
# ── 等待数据库就绪(最多 60 秒)────────────────────────────────
|
||||
wait_for_db() {
|
||||
info "等待数据库连接就绪…"
|
||||
MAX_WAIT=${DB_WAIT_SECONDS:-60}
|
||||
elapsed=0
|
||||
until python - <<'PYEOF'
|
||||
import os, sys, pymysql
|
||||
url = os.environ.get('DATABASE_URL', '')
|
||||
# 从 DATABASE_URL 解析连接参数
|
||||
# 格式: mysql+pymysql://user:pass@host:port/db
|
||||
import re
|
||||
m = re.match(r'mysql\+pymysql://([^:]+):([^@]+)@([^:/]+):?(\d+)?/(\S+)', url)
|
||||
if not m:
|
||||
sys.exit(1)
|
||||
user, pwd, host, port, db = m.groups()
|
||||
port = int(port or 3306)
|
||||
try:
|
||||
conn = pymysql.connect(host=host, port=port, user=user,
|
||||
password=pwd, database=db, connect_timeout=3)
|
||||
conn.close()
|
||||
sys.exit(0)
|
||||
except Exception as e:
|
||||
print(f" 未就绪: {e}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
PYEOF
|
||||
do
|
||||
if [ "$elapsed" -ge "$MAX_WAIT" ]; then
|
||||
err "数据库在 ${MAX_WAIT}s 内未就绪,退出"
|
||||
exit 1
|
||||
fi
|
||||
elapsed=$((elapsed + 3))
|
||||
sleep 3
|
||||
done
|
||||
ok "数据库连接成功"
|
||||
}
|
||||
|
||||
# ── 数据库初始化(幂等)────────────────────────────────────────
|
||||
init_db() {
|
||||
info "执行数据库初始化…"
|
||||
python init_db.py \
|
||||
--admin-user "${ADMIN_USERNAME:-admin}" \
|
||||
--admin-pass "${ADMIN_PASSWORD:-Admin@123456}" \
|
||||
--admin-email "${ADMIN_EMAIL:-admin@example.com}"
|
||||
ok "数据库初始化完成"
|
||||
}
|
||||
|
||||
# ── 创建上传目录 ───────────────────────────────────────────────
|
||||
prepare_dirs() {
|
||||
mkdir -p app/static/uploads/{text,image,audio,video,temp}
|
||||
ok "上传目录就绪"
|
||||
}
|
||||
|
||||
# ── 主流程 ────────────────────────────────────────────────────
|
||||
info "启动个人资料库…"
|
||||
info "Python: $(python --version)"
|
||||
info "Flask env: ${FLASK_ENV:-development}"
|
||||
|
||||
prepare_dirs
|
||||
wait_for_db
|
||||
init_db
|
||||
|
||||
# ── 启动 Gunicorn ─────────────────────────────────────────────
|
||||
WORKERS=${GUNICORN_WORKERS:-4}
|
||||
TIMEOUT=${GUNICORN_TIMEOUT:-120}
|
||||
BIND=${GUNICORN_BIND:-0.0.0.0:5000}
|
||||
|
||||
ok "启动 Gunicorn (workers=${WORKERS}, bind=${BIND})"
|
||||
|
||||
exec gunicorn \
|
||||
--workers "$WORKERS" \
|
||||
--worker-class sync \
|
||||
--timeout "$TIMEOUT" \
|
||||
--bind "$BIND" \
|
||||
--access-logfile - \
|
||||
--error-logfile - \
|
||||
--log-level "${LOG_LEVEL:-info}" \
|
||||
"run:app"
|
||||
0
docker/mysql-init/.gitkeep
Normal file
0
docker/mysql-init/.gitkeep
Normal file
73
docker/nginx.conf
Normal file
73
docker/nginx.conf
Normal file
@@ -0,0 +1,73 @@
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# Nginx 反向代理配置 — 个人资料库
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
upstream flask_app {
|
||||
server app:5000;
|
||||
keepalive 32;
|
||||
}
|
||||
|
||||
# HTTP → HTTPS 重定向(生产环境启用)
|
||||
# server {
|
||||
# listen 80;
|
||||
# server_name your-domain.com;
|
||||
# return 301 https://$host$request_uri;
|
||||
# }
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name _; # 替换为实际域名,如 resource.example.com
|
||||
client_max_body_size 512M; # 需与 MAX_UPLOAD_SIZE_MB 一致
|
||||
|
||||
# ── 安全响应头 ──────────────────────────────────────────────
|
||||
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||
add_header X-Content-Type-Options "nosniff" always;
|
||||
add_header X-XSS-Protection "1; mode=block" always;
|
||||
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
||||
|
||||
# ── 静态文件直接由 Nginx 服务(绕过 Gunicorn,性能更好)──────
|
||||
location /static/ {
|
||||
alias /app/app/static/;
|
||||
expires 7d;
|
||||
add_header Cache-Control "public, immutable";
|
||||
access_log off;
|
||||
|
||||
# 上传的用户文件:禁止目录列表
|
||||
location /static/uploads/ {
|
||||
alias /app/app/static/uploads/;
|
||||
autoindex off;
|
||||
expires 1d;
|
||||
}
|
||||
}
|
||||
|
||||
# ── 代理到 Flask ────────────────────────────────────────────
|
||||
location / {
|
||||
proxy_pass http://flask_app;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
|
||||
proxy_connect_timeout 10s;
|
||||
proxy_read_timeout 120s;
|
||||
proxy_send_timeout 120s;
|
||||
|
||||
# 大文件上传支持
|
||||
proxy_request_buffering off;
|
||||
}
|
||||
|
||||
# ── HTTPS 配置(取消注释并配置证书后启用)────────────────────
|
||||
# listen 443 ssl http2;
|
||||
# ssl_certificate /etc/nginx/ssl/cert.pem;
|
||||
# ssl_certificate_key /etc/nginx/ssl/key.pem;
|
||||
# ssl_protocols TLSv1.2 TLSv1.3;
|
||||
# ssl_ciphers HIGH:!aNULL:!MD5;
|
||||
# ssl_session_cache shared:SSL:10m;
|
||||
|
||||
# ── 健康检查端点 ────────────────────────────────────────────
|
||||
location /health {
|
||||
access_log off;
|
||||
proxy_pass http://flask_app/auth/login;
|
||||
proxy_set_header Host $host;
|
||||
}
|
||||
}
|
||||
@@ -12,3 +12,5 @@ requests==2.32.3
|
||||
python-dotenv==1.0.1
|
||||
python-magic-bin==0.4.14; sys_platform == 'win32'
|
||||
python-magic==0.4.27; sys_platform != 'win32'
|
||||
# WSGI 服务器(容器生产环境)
|
||||
gunicorn==23.0.0
|
||||
|
||||
Reference in New Issue
Block a user