指定Docker Compose卷后为何无法找到uvicorn?
问题背景
我基于多阶段构建做了一个优化后的FastAPI镜像(最终大小170MB),Dockerfile代码如下:
# The builder image, used to build the virtual environment FROM python:3.11-bullseye as builder # Install package manager RUN pip install poetry==1.7.1 # Define environment ENV POETRY_NO_INTERACTION=1 \ POETRY_VIRTUALENVS_IN_PROJECT=1 \ POETRY_VIRTUALENVS_CREATE=1 \ POETRY_CACHE_DIR=/tmp/poetry_cache # Set work directory WORKDIR /app # Copy project dependencies COPY pyproject.toml poetry.lock ./ # Install production dependencies only and remove poetry cache directory RUN poetry install --without dev --no-root && rm -rf $POETRY_CACHE_DIR # The runtime image, used to run the service FROM python:3.11-slim-bullseye as runtime # Set virtual environment path and add it to the system Path ENV VIRTUAL_ENV=/app/.venv \ PATH="/app/.venv/bin:$PATH" # Copy project dependencies from builder stage COPY --from=builder ${VIRTUAL_ENV} ${VIRTUAL_ENV} # Copy project code COPY app/ /app/ # Run service CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80"]
未挂载本地代码卷时,docker compose up能正常启动FastAPI服务。但为了实现代码变更自动重启,我给Compose添加了本地代码挂载,修改后的compose.yaml如下:
services: api: image: pws build: context: . dockerfile: Dockerfile restart: on-failure ports: - "80:80" volumes: - ./app/:/app/ command: ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80", "--reload"]
添加挂载后启动报错,错误信息:
Error response from daemon: failed to create task for container: failed to create shim task: OCI runtime create failed: runc create failed: unable to start container process: exec: "uvicorn": executable file not found in $PATH: unknown
错误原因
挂载./app/:/app/时,本地的app目录会完全覆盖容器内的/app目录。而容器里的uvicorn是安装在/app/.venv虚拟环境中的,本地项目的app目录里并没有这个.venv文件夹,导致系统找不到uvicorn可执行文件。
解决方案
方案1:调整挂载路径,保留容器内的虚拟环境
修改Compose的volumes配置,把本地./app挂载到容器内的/app/app,而不是直接覆盖/app:
services: api: image: pws build: context: . dockerfile: Dockerfile restart: on-failure ports: - "80:80" volumes: # 挂载本地代码到容器的/app/app,保留/app下的.venv虚拟环境 - ./app/:/app/app/ command: ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80", "--reload"]
容器内的项目结构原本就是/app/app/main.py,这样挂载后既可以实时同步本地代码变更,又不会破坏虚拟环境的路径,uvicorn就能正常被找到。
方案2:修改Dockerfile的工作目录(可选)
如果希望挂载路径更直观,可以调整Dockerfile的工作目录,让代码直接放在/app下,同时把虚拟环境移到上级目录:
修改Dockerfile的builder和runtime阶段:
# builder阶段 WORKDIR /src # runtime阶段 WORKDIR /app ENV VIRTUAL_ENV=/src/.venv \ PATH="/src/.venv/bin:$PATH" COPY --from=builder ${VIRTUAL_ENV} ${VIRTUAL_ENV} COPY app/ /app/
然后Compose挂载./app/:/app/,这样虚拟环境在/src/.venv,不会被挂载的代码覆盖,同样能解决问题。
方案3:直接安装uvicorn到系统Python(不推荐)
如果只是临时开发用,不想调整路径,可以在runtime阶段把uvicorn直接安装到系统Python环境,这样即使虚拟环境被覆盖,也能找到可执行文件。但这会破坏镜像的大小优化,不建议用于生产环境:
修改Dockerfile的runtime阶段:
FROM python:3.11-slim-bullseye as runtime # 直接安装uvicorn到系统Python RUN pip install uvicorn ENV VIRTUAL_ENV=/app/.venv \ PATH="/app/.venv/bin:$PATH" COPY --from=builder ${VIRTUAL_ENV} ${VIRTUAL_ENV} COPY app/ /app/ CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80"]
内容的提问来源于stack exchange,提问作者Santiago Ortiz Ceballos

