Files

130 lines
5.3 KiB
Docker
Raw Permalink 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.
# 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