dockerfile中须显式声明python小版本和依赖,先copy requirements.txt再pip安装,按依赖→代码顺序优化缓存,cmd用exec格式确保pid1,env设默认值并配合外部覆盖,workdir和user不可省略。

Python 版本和依赖必须在 Dockerfile 里显式声明
很多人直接抄网上的基础镜像写法,用 FROM python:latest 或 FROM python:3,结果本地跑得好好的,构建时突然报错:ModuleNotFoundError: No module named 'requests'。这不是 Python 没装对,是镜像里压根没装你项目需要的包。
真正可靠的做法是:固定 Python 小版本(比如 python:3.11-slim),再用 COPY requirements.txt . + RUN pip install --no-cache-dir -r requirements.txt 安装依赖。别跳过 requirements.txt —— 即使只用一个包,也得列出来。
-
python:slim镜像体积小、攻击面小,比python:latest更适合生产 - 不要在
RUN pip install后加--user,容器里默认就是 root,--user反而会让包装到非标准路径,后续 import 失败 - 如果项目有
pyproject.toml且用pip安装(不是poetry或uv),仍建议先生成requirements.txt,避免构建缓存失效频繁重装
COPY 顺序影响构建速度和缓存命中率
Docker 构建靠层缓存提速,但只要某一层变了,它后面所有层都会重建。如果你把源码 COPY . . 放在 pip install 前面,哪怕只改了一个空格,pip install 这步就永远没法复用缓存 —— 每次都重装全部依赖,浪费时间又容易出错。
正确顺序是:先拷依赖文件,装完依赖,最后才拷代码。这样改代码不影响前面的层。
立即学习“Python免费学习笔记(深入)”;
- 必须把
requirements.txt(或pyproject.toml+poetry.lock)单独COPY,早于其他文件 - 用
.dockerignore排除__pycache__、.git、venv等,否则它们被 COPY 进镜像,可能干扰运行时行为 - 别用
COPY *.py .,通配符在不同 shell 下行为不一致;老老实实COPY app.py .或COPY src/ .
启动命令写错会导致容器秒退
常见错误是写成 CMD python main.py,一运行就退出,docker logs 里啥也没有。其实不是程序崩了,是容器认为主进程结束了,直接停机。
根本原因是:Python 脚本执行完就退出,Docker 把它当主进程,主进程结束 = 容器生命周期结束。
- 如果是 Web 服务(Flask/FastAPI),确保启动命令带
--host 0.0.0.0:8000(不是127.0.0.1),否则外部连不上 - 用
exec形式启动:写成CMD ["python", "main.py"],而不是CMD python main.py—— 前者让 Python 成为 PID 1,能正确接收信号(比如docker stop) - 调试时加个
tail -f /dev/null临时占位,确认容器能稳住,再换回真实命令
环境变量和端口暴露不能只靠 docker run 参数补救
有人觉得“反正我 docker run -e ENV=prod -p 8000:8000 就行”,但这样会导致本地开发和容器行为不一致。比如 os.getenv("DEBUG") 在容器里是空字符串,而本地是 "True",结果日志不打、配置加载错,问题很难复现。
应该在 Dockerfile 里用 ENV 设默认值,在 docker-compose.yml 或 run 命令里覆盖 —— 不是替代,是分层控制。
-
EXPOSE 8000不等于开放端口,只是文档说明;真正要访问,还得docker run -p或ports:配置 -
ENV PYTHONUNBUFFERED=1很关键,否则 print 日志会缓存,docker logs看不到实时输出 - 敏感变量(如数据库密码)绝不要写进
Dockerfile,用--env-file或secrets注入
最常被忽略的是工作目录和用户权限:不写 WORKDIR /app,COPY 的文件可能落在奇怪位置;不加 USER 1001,容器以 root 运行会有安全扫描告警,某些 K8s 环境甚至直接拒绝部署。










