# syntax=docker/dockerfile:1.7 # audio2text — 一份 Dockerfile,CPU(dev) / GPU(prod) 双形态。 # docker build --build-arg VARIANT=cpu -t audio2text:cpu . # docker build --build-arg VARIANT=gpu -t audio2text:gpu . # docker compose --profile dev up -d --build # 开发:dev target + 源码挂载 + reload # # 分层目标:依赖层(deps)稳定固化,代码层(final/dev)在最末,改代码不重装依赖。 # BuildKit 缓存挂载(--mount=type=cache):pip wheel 跨构建复用,二次构建秒级。 # # 安全约束:PyTorch CPU wheel 与 GPU(CUDA) wheel 是两个不兼容二进制包。 # - CPU 版 torch.cuda.is_available()=False(CPU 预期) # - GPU 版 torch.cuda.is_available()=True(GPU 可调度) # 因此 torch 必须按 VARIANT 分叉装不同 wheel,绝不能跨 variant 共享依赖层。 # deps 阶段用 FROM base-${VARIANT},CPU/GPU 是两条独立构建链,各自装对应 torch。 ARG VARIANT=cpu # --------------------------------------------------------------------------- # 1. 基础镜像分叉:CPU 用 slim Python,GPU 用 CUDA runtime + 手动装 python3.12 # --------------------------------------------------------------------------- FROM python:3.12-slim AS base-cpu FROM nvidia/cuda:12.1.0-runtime-ubuntu22.04 AS base-gpu # Ubuntu 22.04 默认源只有 python3.10/3.11,需加 deadsnakes PPA 才能装 python3.12。 # 注意:不能用 python3-pip(那是 3.10 的系统 pip,会把包装到 3.10 site-packages)。 # 用 python3.12 -m ensurepip 给 3.12 装 pip,确保所有包进 3.12 目录。 # DEBIAN_FRONTEND=noninteractive 避免 tzdata 等包进入交互式配置卡住构建。 ENV DEBIAN_FRONTEND=noninteractive RUN apt-get update -y && apt-get install -y --no-install-recommends \ software-properties-common gnupg ca-certificates \ && add-apt-repository -y ppa:deadsnakes/ppa \ && apt-get update -y && apt-get install -y --no-install-recommends \ python3.12 python3.12-venv python3.12-dev \ && rm -rf /var/lib/apt/lists/* \ && ln -sf /usr/bin/python3.12 /usr/local/bin/python3 \ && ln -sf /usr/bin/python3.12 /usr/local/bin/python \ && python3 -m ensurepip \ && python3 -m pip install --upgrade pip # --------------------------------------------------------------------------- # 2. deps:系统依赖 + Python 依赖(含 torch)。CPU/GPU 各自一条独立链。 # final / dev 都 FROM deps,继承对应 variant 的 torch,不串。 # --------------------------------------------------------------------------- FROM base-${VARIANT} AS deps ARG VARIANT ENV PYTHONUNBUFFERED=1 \ PIP_NO_CACHE_DIR=1 \ HF_HOME=/models/huggingface \ CT2_CACHE=/models/ctranslate2 # ffmpeg 是核心系统依赖;patchelf 用于修复 ctranslate2 可执行栈 RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ --mount=type=cache,target=/var/lib/apt,sharing=locked \ apt-get update -y && apt-get install -y --no-install-recommends \ ffmpeg ca-certificates patchelf \ && rm -rf /var/lib/apt/lists/* WORKDIR /app COPY requirements.txt /app/requirements.txt # 【顺序关键】必须先装 torch(按 VARIANT 分叉),再装 requirements。 # 原因:requirements 里的 transformers / accelerate 依赖 torch,若先装 requirements, # pip 会从默认 PyPI 拉来 GPU 版 torch + nvidia-* 全家桶(~2GB),即使后续覆盖装 CPU torch, # 那些无用的 nvidia 包仍残留在镜像里。先装 torch 让 pip 解析 requirements 时 torch 已满足。 # # CPU 走 cpu index(~200MB,无 nvidia 依赖);GPU 走默认 PyPI(带 CUDA,~2.5GB)。 # 两套 wheel 二进制不兼容:CPU 版 cuda.is_available()=False,GPU 版 =True。不可共享。 RUN --mount=type=cache,target=/root/.cache/pip,sharing=locked \ pip install --upgrade pip && \ if [ "$VARIANT" = "cpu" ]; then \ pip install torch --index-url https://download.pytorch.org/whl/cpu ; \ else \ pip install torch ; \ fi # torch 已就位,requirements 里的 transformers/accelerate 解析时复用已装 torch,不重复拉 RUN --mount=type=cache,target=/root/.cache/pip,sharing=locked \ pip install -r /app/requirements.txt # ctranslate2 的 .so 带可执行栈标志(PT_GNU_STACK X),在某些内核 + Docker 组合下 # 会触发 "cannot enable executable stack as shared object requires"。用 patchelf # 清掉该标志(改为 RW),无需放宽容器安全策略。 # 注意:库在 ctranslate2.libs/ 隐藏目录(pip wheel 拆分产物),不在 ctranslate2/ 包目录。 RUN for d in /usr/local/lib/python3.12/site-packages/ctranslate2.libs \ /usr/local/lib/python3.12/site-packages/ctranslate2; do \ [ -d "$d" ] && find "$d" -name '*.so*' \ -exec patchelf --clear-execstack {} \; 2>/dev/null || true; \ done; \ python -c "import ctranslate2; print('ctranslate2 stack fix verified', ctranslate2.__version__)" # --------------------------------------------------------------------------- # 3. dev:从 deps 继承依赖,不 COPY 代码(compose 用 volume 挂载源码)。 # 开启 uvicorn --reload,改代码零重建、保存即生效。 # 注意:dev 必须在 final 之前,保证 `docker build` 默认 target 是 final。 # --------------------------------------------------------------------------- FROM deps AS dev VOLUME ["/data", "/models"] # ctranslate2/faster-whisper 用系统动态链接器找 cuDNN(不走 torch 的库加载), # 需把 torch wheel 自带的 nvidia 库目录加入 LD_LIBRARY_PATH,否则 GPU 推理报 # "Unable to load libcudnn_ops.so.9"。放在 final/dev 而非 deps,避免 ENV 变化 # 导致 deps 的 apt/pip 层缓存失效。CPU 镜像无此目录,路径被忽略不影响。 ENV CONFIG_PATH=/app/config.yaml \ LD_LIBRARY_PATH=/usr/local/lib/python3.12/site-packages/nvidia/cudnn/lib:/usr/local/lib/python3.12/dist-packages/nvidia/cudnn/lib:/usr/local/lib/python3.12/site-packages/nvidia/cublas/lib:/usr/local/lib/python3.12/dist-packages/nvidia/cublas/lib EXPOSE 8000 CMD ["python", "-m", "uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--reload"] # --------------------------------------------------------------------------- # 4. final(prod):从 deps 继承全部依赖,只加 app 代码。 # 放在最后 = `docker build` 默认 target。setup.sh / start.sh 依赖此默认行为。 # --------------------------------------------------------------------------- FROM deps AS final COPY app /app/app COPY scripts /app/scripts COPY config.example.yaml /app/config.example.yaml # 运行时数据:上传 / 中间产物 / 输出字幕 / 模型缓存 # 全部走 volume,镜像本身无状态、无敏感数据 VOLUME ["/data", "/models"] ENV CONFIG_PATH=/app/config.yaml \ LD_LIBRARY_PATH=/usr/local/lib/python3.12/site-packages/nvidia/cudnn/lib:/usr/local/lib/python3.12/dist-packages/nvidia/cudnn/lib:/usr/local/lib/python3.12/site-packages/nvidia/cublas/lib:/usr/local/lib/python3.12/dist-packages/nvidia/cublas/lib EXPOSE 8000 CMD ["python", "-m", "uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]