Docker构建中如何正确处理console_scripts并避免重复pip安装
问题
我在setup.cfg中配置了入口点:
[options.entry_points] console_scripts = mycmd = my_proj.main:program.run
main.py内容:
from invoke import Collection, Program from my_proj import jobs program = Program(namespace=Collection.from_module(jobs), version="1.2.3")
项目结构:
my_proj main.py jobs/ myjob.py log_conf logger.ini logsetup.py
按照最佳实践编写Dockerfile时遇到问题:
RUN pip install wheel COPY setup.cfg setup.py pyproject.toml MANIFEST.in ./ RUN --mount=type=cache,target=/root/.cache \ pip install '.' COPY ./my_proj ./my_proj
运行mycmd时出现错误:
Traceback (most recent call last): File "/usr/local/bin/mycmd", line 5, in <module> from my_proj.main import program ModuleNotFoundError: No module named 'my_proj'
尝试仅复制入口文件:
COPY my_proj/main.py ./my_proj/main.py
又出现错误:
pip install ImportError: cannot import name 'jobs' from 'myjob' (unknown location)
目前只能在pip install前复制整个my_proj目录,但这会导致代码改动时必须重新执行pip install,缓存失效,体验很差。想知道如何避免在pip install前复制整个项目目录。
解决方案
方案1:使用可编辑安装(最简单快速)
将pip install '.'改为pip install -e '.',可编辑安装会在Python的site-packages中创建指向当前项目目录的链接,后续复制my_proj目录后,Python能通过链接找到模块:
RUN pip install wheel COPY setup.cfg setup.py pyproject.toml MANIFEST.in ./ RUN --mount=type=cache,target=/root/.cache \ pip install -e '.' COPY ./my_proj ./my_proj
方案2:多阶段构建(适合生产环境)
通过多阶段构建分离wheel打包和运行环境安装,仅当配置文件或项目代码改动时才重新构建wheel,后续安装步骤利用缓存:
# 构建阶段:生成wheel包 FROM python:3.11-slim AS builder WORKDIR /app RUN pip install --upgrade pip wheel COPY setup.cfg setup.py pyproject.toml MANIFEST.in ./ COPY my_proj ./my_proj RUN python setup.py bdist_wheel # 运行阶段:安装wheel包并运行 FROM python:3.11-slim WORKDIR /app COPY --from=builder /app/dist/*.whl ./ RUN --mount=type=cache,target=/root/.cache \ pip install --no-cache-dir *.whl CMD ["mycmd"]
方案3:避免安装时导入完整项目代码(根源解决)
如果你的setup.cfg/setup.py需要从项目中读取信息(如版本号),不要导入完整模块,将这类信息拆分到单独文件:
- 在
my_proj/__init__.py中定义版本号:
__version__ = "1.2.3"
- 修改
setup.cfg的版本配置:
[metadata] version = attr: my_proj.__version__
- 调整Dockerfile,先复制必要文件完成安装,再复制完整代码:
RUN pip install wheel COPY setup.cfg setup.py pyproject.toml MANIFEST.in ./ # 仅复制包含版本信息的__init__.py,满足安装时的依赖 COPY my_proj/__init__.py ./my_proj/__init__.py RUN --mount=type=cache,target=/root/.cache \ pip install '.' # 后续复制剩余项目文件 COPY my_proj/main.py ./my_proj/main.py COPY my_proj/jobs ./my_proj/jobs COPY my_proj/log_conf ./my_proj/log_conf
这样pip install时仅需导入my_proj.__init__.py,不会触发其他模块的导入,只有配置文件或__init__.py改动时才会重新执行pip install,代码改动时仅需复制文件,无需重新安装依赖。
内容的提问来源于stack exchange,提问作者red888
相关产品推荐
相关产品推荐

