130 lines
5.3 KiB
Docker
130 lines
5.3 KiB
Docker
# syntax=docker/dockerfile:1.7
|
||
# ===== 阶段1: 构建前端 =====
|
||
FROM node:20-slim AS frontend-builder
|
||
|
||
WORKDIR /build
|
||
COPY web/frontend/package.json web/frontend/package-lock.json* ./
|
||
# npm 缓存挂载:即使镜像层缓存失效,也复用已下载的包,避免每次部署都重新拉依赖。
|
||
RUN --mount=type=cache,target=/root/.npm \
|
||
npm config set registry https://registry.npmmirror.com && \
|
||
(npm ci || npm install) || \
|
||
(npm config set registry https://registry.npmjs.org && (npm ci || npm install))
|
||
COPY web/frontend/ ./
|
||
RUN npm run build
|
||
|
||
# ===== 阶段2: 构建应用宝 Worker 的 Node 依赖 =====
|
||
FROM node:20-slim AS yyb-node-builder
|
||
|
||
WORKDIR /build
|
||
COPY services/yyb-worker/package.json services/yyb-worker/package-lock.json ./
|
||
# 优先 npmmirror,失败时回退官方 registry,避免国内构建时依赖下载中断。
|
||
RUN --mount=type=cache,target=/root/.npm \
|
||
npm config set registry https://registry.npmmirror.com && \
|
||
npm ci --omit=dev || \
|
||
(npm config set registry https://registry.npmjs.org && npm ci --omit=dev)
|
||
|
||
# ===== 阶段3: Python 运行时 =====
|
||
FROM python:3.12-slim AS runtime
|
||
|
||
ARG USE_CHINA_MIRRORS=true
|
||
ARG UV_VERSION=0.13.5
|
||
|
||
# 服务器默认使用阿里云镜像;本地构建可设 USE_CHINA_MIRRORS=false 直连官方源。
|
||
# apt 缓存挂载:包索引和 .deb 都留在 BuildKit 缓存里,不再每次重下。
|
||
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
|
||
--mount=type=cache,target=/var/lib/apt,sharing=locked \
|
||
if [ "$USE_CHINA_MIRRORS" = "true" ]; then \
|
||
cp /etc/apt/sources.list.d/debian.sources /tmp/debian.sources && \
|
||
sed -i 's/deb.debian.org/mirrors.cloud.aliyuncs.com/g' /etc/apt/sources.list.d/debian.sources && \
|
||
(apt-get update -o APT::Update::Error-Mode=any || \
|
||
(sed -i 's/mirrors.cloud.aliyuncs.com/mirrors.aliyun.com/g' /etc/apt/sources.list.d/debian.sources && \
|
||
apt-get update -o APT::Update::Error-Mode=any) || \
|
||
(cp /tmp/debian.sources /etc/apt/sources.list.d/debian.sources && \
|
||
apt-get update -o APT::Update::Error-Mode=any)); \
|
||
else \
|
||
apt-get update -o APT::Update::Error-Mode=any; \
|
||
fi && \
|
||
apt-get install -y --no-install-recommends \
|
||
libgl1 \
|
||
libglib2.0-0 \
|
||
nodejs \
|
||
fonts-wqy-microhei
|
||
|
||
# 安装 uv:服务器走阿里云镜像,本地直连官方 PyPI。
|
||
# mirrors.cloud.aliyuncs.com 的证书与主机名不匹配,直接用 mirrors.aliyun.com,
|
||
# 避免每次构建先失败重试 10 秒;版本固定,基础层重建时行为保持一致。
|
||
RUN --mount=type=cache,target=/root/.cache/pip \
|
||
if [ "$USE_CHINA_MIRRORS" = "true" ]; then \
|
||
(pip install --retries 2 --timeout 30 \
|
||
-i https://mirrors.aliyun.com/pypi/simple/ "uv==${UV_VERSION}" || \
|
||
pip install --retries 2 --timeout 30 "uv==${UV_VERSION}"); \
|
||
else \
|
||
pip install --retries 2 --timeout 30 "uv==${UV_VERSION}"; \
|
||
fi
|
||
|
||
WORKDIR /app
|
||
|
||
# 缓存目录和 .venv 位于不同文件系统时 uv 会自动回退为复制;显式设置可避免构建告警。
|
||
ENV UV_LINK_MODE=copy
|
||
|
||
# 先复制依赖文件,利用 Docker 缓存层
|
||
COPY pyproject.toml uv.lock* README.md alembic.ini ./
|
||
RUN --mount=type=cache,target=/root/.cache/uv \
|
||
uv venv /app/.venv && \
|
||
if [ "$USE_CHINA_MIRRORS" = "true" ]; then \
|
||
(UV_INDEX_URL=https://mirrors.aliyun.com/pypi/simple/ uv pip install -e . || \
|
||
uv pip install -e .); \
|
||
else \
|
||
uv pip install -e .; \
|
||
fi
|
||
|
||
# 复制项目代码
|
||
COPY core/ ./core/
|
||
COPY utils/ ./utils/
|
||
COPY web/backend/ ./web/backend/
|
||
COPY scripts/migrate_sqlite_to_mysql.py ./scripts/migrate_sqlite_to_mysql.py
|
||
|
||
# 将应用宝 Worker 合并进同一应用镜像;其 Python 依赖已纳入项目 uv.lock。
|
||
COPY services/yyb-worker/runtime/ ./services/yyb-worker/runtime/
|
||
COPY --from=yyb-node-builder /build/node_modules ./services/yyb-worker/runtime/node_modules
|
||
COPY docker-entrypoint.sh ./docker-entrypoint.sh
|
||
|
||
# 复制前端构建产物
|
||
COPY --from=frontend-builder /build/dist ./web/frontend/dist
|
||
|
||
# 创建数据和日志目录
|
||
RUN mkdir -p /app/data /app/logs /app/data/yyb-worker-jobs && \
|
||
chmod +x /app/docker-entrypoint.sh
|
||
|
||
# 环境变量
|
||
ENV PYTHONUNBUFFERED=1
|
||
ENV PYTHONDONTWRITEBYTECODE=1
|
||
ENV PATH="/app/.venv/bin:$PATH"
|
||
# 生产镜像默认关闭文件监控重载,避免 data/logs 写入触发重启循环。
|
||
ENV UVICORN_RELOAD=false
|
||
ENV YYB_WORKER_URL=http://127.0.0.1:8810
|
||
|
||
EXPOSE 8800
|
||
|
||
# 健康检查同时覆盖 Web API 与同容器 Worker。
|
||
HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
|
||
CMD python -c "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8800/api/health'); urllib.request.urlopen('http://127.0.0.1:8810/health')" || exit 1
|
||
|
||
# 同一容器内启动 Web 与本机 Worker;入口脚本负责联动退出和信号转发。
|
||
ENTRYPOINT ["/app/docker-entrypoint.sh"]
|
||
|
||
# ===== 阶段4: 测试镜像(含 dev 依赖与测试代码,可容器内跑 pytest)=====
|
||
# 用法: docker compose --profile test run --rm test
|
||
FROM runtime AS test
|
||
|
||
ARG USE_CHINA_MIRRORS=true
|
||
COPY tests/ ./tests/
|
||
COPY conftest.py ./
|
||
RUN --mount=type=cache,target=/root/.cache/uv \
|
||
if [ "$USE_CHINA_MIRRORS" = "true" ]; then \
|
||
(UV_INDEX_URL=https://mirrors.aliyun.com/pypi/simple/ uv sync --frozen --group dev || \
|
||
uv sync --frozen --group dev); \
|
||
else \
|
||
uv sync --frozen --group dev; \
|
||
fi
|