Docker Dockerfile Ptyhon实战配置-1
Last updated
Was this helpful?
Last updated
Was this helpful?
本示例配置以 python:3.12-slim-bookworm
为基础镜像构建Docker镜像
官方文档:
(重点阅读 "Sort multi-line arguments" 部分)
ENV 设置的环境变量默认不会自动跨阶段共享
PYTHONUNBUFFERED=1
作用:禁用 Python 的标准输出缓冲(stdout/stderr)。
默认值:PYTHONUNBUFFERED=0(即启用缓冲)。
效果:
日志和打印输出会立即显示(不等待缓冲区满),适合容器或实时日志场景。
避免日志延迟,尤其在 Docker 或 CI/CD 环境中非常有用。
建议:在 Docker 环境中,通常设置为 1,因为你可能希望日志即时显示,避免等待缓冲区写满。
PYTHONDONTWRITEBYTECODE=1
作用:禁止 Python 生成 .pyc
字节码缓存文件。
默认值:PYTHONDONTWRITEBYTECODE=0(即启用 .pyc 文件的生成)。
效果:
避免在容器或临时环境中生成冗余的 __pycache__
目录。
减少磁盘写入,提升性能(但可能略微增加启动时间,因为没有 .pyc 文件,Python 需要重新解析源代码)。
建议:在 Docker 和类似环境中,建议设置为 1,避免多余的文件生成,尤其是在容器中,减少磁盘空间占用。
PIP_NO_CACHE_DIR=1
作用:禁用 pip 的缓存功能。
默认值:PIP_NO_CACHE_DIR=0(即启用缓存)。
效果:
安装包时不会缓存到本地(如 ~/.cache/pip
),节省磁盘空间。
适合一次性构建的容器环境,避免缓存污染。
建议:对于容器化应用,建议设置为 1,避免缓存的 pip 包影响镜像体积,并减少构建时的磁盘空间消耗。
PIP_DISABLE_PIP_VERSION_CHECK=1
作用:禁止 pip 检查自身版本更新。
效果:
跳过每次运行时检查 pip 新版本的网络请求。
减少构建时间,避免因网络问题导致的延迟。
建议:为了加速构建过程并避免不必要的网络请求,建议设置为 1,尤其在容器或自动化构建流程中。
UV_VERSION=0.2.0
作用:指定 uv
(Rust 编写的 Python 工具链)的版本。
效果:
uv 是一个高效的工具链,用于快速安装和管理 Python 项目依赖。通过指定版本,可以确保工具链版本的一致性。
如果 uv 版本与项目的要求不匹配,可能会导致构建问题,因此指定版本是确保构建稳定性的好做法。
默认值:此变量没有默认值,具体值需要根据项目需求指定。
建议:推荐指定特定版本的 uv,以确保工具链版本的兼容性和一致性。可以根据项目需求选择稳定的版本(如 0.6.9 等)。
这些变量通常用于 容器化部署 或 CI/CD 流水线,目的是:
如果是本地开发环境,部分变量(如 PYTHONDONTWRITEBYTECODE)可能会影响调试体验,需根据实际需求调整。
减少冗余文件(如 .pyc、pip 缓存)。
提升日志实时性(无缓冲输出)。
避免不必要的网络请求(如 pip 版本检查)。
固定工具版本(确保环境一致性)。
apt-get update:更新包索引。
apt-get install -y --no-install-recommends:
-y:自动回答“yes”,避免安装过程中需要手动确认。
安装所需的包并避免安装推荐的额外包,以减小镜像大小。
build-essential:包含 GCC、make 等编译工具链(编译 Python C 扩展必备)。
libpq-dev:PostgreSQL 客户端库的开发文件(用于 psycopg2 等 Python 数据库驱动)。
libffi-dev:安装libffi的开发文件,某些Python包(如cryptography)需要这个库
rm -rf /var/lib/apt/lists/*:删除 apt 的索引缓存,减少层大小。
apt-get clean:清理 apt 包管理器下载的临时缓存,进一步减小镜像体积。它会删除下载的 .deb 包文件,避免它们被保留在镜像中。
减少不必要的层(Layer)
每一个 RUN
命令都会生成一个新的层,过多的 RUN
命令会导致镜像过大。合并多个 RUN
命令,减少镜像层的数量。
使用 apt-get clean
在安装完系统依赖后,执行 apt-get clean
以减少镜像大小。因为 apt-get update
会下载很多临时文件,安装后应该删除这些文件。
它会删除 /var/cache/apt/archives/ 目录中的所有 .deb 包缓存文件,这些文件是 apt-get 安装软件包时下载的包。
这些缓存文件通常是安装完软件包后不再需要的,删除它们可以有效减少镜像的体积。
减少镜像体积
使用 --no-install-recommends
选项来避免安装不必要的包。
rm -rf /var/lib/apt/lists/*:
这个命令会删除 /var/lib/apt/lists/
目录中的包索引文件。
包索引文件是 apt-get update 时下载的,包含了关于可用包的信息(如包的版本、依赖关系等)。
删除这些文件会导致以后无法通过 apt-get update 命令直接从缓存获取包的索引,通常在安装软件包后删除它们有助于减少镜像体积。
优化 Python 安装依赖 --no-cache-dir
FROM base AS builder
在 Docker 多阶段构建中,重复定义 FROM ... AS
的目的是为了明确划分不同的构建阶段,隔离实现镜像精简,每个阶段可以独立选择基础镜像、安装依赖和执行任务。以下是具体原因:
阶段独立性
每个 FROM
指令会创建一个 全新的构建阶段 ,前一个阶段的文件系统、环境变量和操作不会自动继承到下一个阶段
例如:
这里 builder
和 runtime
是两个完全隔离的环境,runtime
阶段不会包含 builder
阶段安装的临时工具(如编译器)。
优化镜像体积
多阶段构建的核心目的是仅保留最终需要的文件。通过分阶段操作,可以丢弃中间阶段的冗余内容(如编译工具链、缓存文件)。
如果只定义一个阶段,所有中间文件都会保留在最终镜像中,导致体积臃肿。
灵活的基础镜像选择
不同阶段可能需要不同的基础镜像。例如:
构建阶段:使用包含编译工具的镜像(如 golang:1.21)。 * 运行阶段:使用极简镜像(如 alpine 或 distroless)。
单阶段构建无法实现这种灵活性
代码可读性与维护性
显式命名阶段(如 AS builder)可以让 Dockerfile 更易读,便于后续维护或扩展。
例如,后续可以通过 --target=builder 参数单独构建某个阶段
总结
重复定义 FROM ... AS
是 Docker 多阶段构建的核心设计,目的是通过阶段隔离实现镜像精简、安全性和灵活性。如果仅定义一个阶段,将失去多阶段构建的所有优势
pip install -e .[dev] 依赖的作用
开发工具链:
pytest
:单元测试框架。
black
/autopep8
:代码风格自动化和格式化。
ipdb
:调试工具(类似 pdb
,但支持 IPython 增强)。
pylint
:代码质量检查(如命名规范、未使用变量等)。
安装方式:
使用 pip
安装开发依赖(需指定 dev
组):
COPY . .
在 Dockerfile 中使用 COPY . .
语句时,两个点(.
)的含义如下:
第一个点 (.
):表示源路径,即从 Docker 构建上下文(通常是当前的目录)中复制文件或目录。在构建过程中,Docker 会将当前目录(即 Dockerfile 所在目录)作为构建上下文,除非通过 docker build
命令显式指定其他目录。
第二个点 (.
):表示目标路径,即复制到镜像中的路径。COPY . .
语句中的第二个点表示将源路径中的内容复制到镜像中的当前工作目录。在 Dockerfile 中,你设置了 WORKDIR /app
,因此第二个点表示将源文件复制到镜像中的 /app
目录。
具体举例说明:
假设你的项目结构是:
构建上下文是 /project
,即你运行 docker build
命令的目录。
你在 Dockerfile 中写了 COPY . .
,这意味着:
源路径:当前构建上下文(/project
目录),也就是你在构建时指定的目录。
目标路径:镜像中的 /app
目录(因为你之前设定了 WORKDIR /app
),将构建上下文的内容复制到这个目录。
结果:
/project
目录中的所有文件和子目录(除了 .dockerignore
中指定的文件)都会被复制到镜像中的 /app
目录。
注意:
.dockerignore
文件可以控制哪些文件不会被复制到镜像中。它的作用类似于 .gitignore
,可以避免将不需要的文件(如构建文件、日志等)复制到镜像中,从而减小镜像体积。
例如,.dockerignore
文件中可能包含:
这将确保 .pyc
文件、__pycache__
目录和 .git
文件夹不会被复制到镜像中。
小结:
COPY . .
语句将当前目录(构建上下文)的所有内容复制到镜像中的当前工作目录(WORKDIR
)下。
第一个点是源路径,第二个点是目标路径。
ENV PYTHONPATH "${PYTHONPATH}:/app"
在 Dockerfile 中,通常把应用代码拷贝到镜像的 /app
目录下(通过 WORKDIR /app
)。如果不显式把 /app
加入 PYTHONPATH
,当你以模块方式运行(如 uvicorn main:app
)或在其他目录调用 Python 时,Python 并不总是会把 /app
作为模块根目录。这时就可能出现 ModuleNotFoundError
。
原理:
是在容器内设置(或扩展)Python 的模块搜索路径,使得 /app
目录始终被加入到 Python 启动时的 sys.path
列表中。这样,无论你在何处运行 python
,都可以直接通过 import your_module
来加载 /app
下的代码,而不必安装成 package 或手动修改 sys.path
。
示例:
而有了
则 /app
会被加入到 sys.path
,保证 import main
或者 from your_package import ...
都能正常找到代码。
小结
作用:确保容器内无论何时何地 /app
都在 Python 的模块搜索路径里。
好处:
避免在代码里硬编码 sys.path.append()
。
以模块方式启动(uvicorn main:app
、python -m your_package
)时不会因找不到模块而报错。
在 Dockerfile 中配置用户和权限的步骤,如:
是一个最佳实践,并且对于大多数生产环境来说是非常推荐的,尤其是考虑到安全性方面的原因。
为什么配置用户和权限是有必要的:
避免使用 root 用户运行应用:
Docker 容器默认以 root
用户运行,root
用户拥有对系统的完全控制权限。如果你的容器内的应用存在安全漏洞,攻击者可以利用容器中的 root
用户进行更严重的操作(如获取容器外的资源或控制主机)。
通过在 Dockerfile 中创建一个普通用户(如 appuser
),并将应用切换到该用户运行,可以有效地限制容器内应用的权限,即使容器被攻击,攻击者也只能获得该普通用户的权限,减少潜在的安全风险。
遵循最小权限原则(Principle of Least Privilege):
在安全领域,最小权限原则意味着给程序或用户最少的权限,只允许其执行必要的操作。将容器中的应用切换到非 root
用户,可以确保即使应用存在漏洞,攻击者也无法获得不必要的权限。
增强容器隔离性:
即使容器在运行时暴露了安全漏洞,非 root
用户的容器更难被用来突破容器的隔离层,对宿主系统进行攻击。
容器生产环境的标准做法:
在很多企业和安全合规性较高的生产环境中,强制执行容器应用以非 root
用户运行已成为行业标准。例如,某些云平台和容器扫描工具会检查容器是否以 root
用户运行,如果是 root
用户,可能会标记为不符合安全标准。
目录权限管理:
通过 chown -R appuser:appuser /app
,可以确保 /app
目录和其中的所有文件的拥有者是 appuser
,避免在容器内出现权限不一致的问题。通常,root
用户可能会拥有 /app
中的文件权限,这样当切换到 appuser
后,appuser
无法访问这些文件。通过修改文件权限,确保容器内应用的正常运行。
具体选择哪个版本,请看: