需要减少一个Python应用程序的Docker镜像大小,同时必须支持离线安装,无论是在PYPI包安装中还是在APT更新和安装包中。
让我们逐行进行优化。
Docker构建 buildx
语法
- 首先确保Docker使用
docker buildx
作为其默认的docker build
。
如果您已安装Docker Desktop,则无需启用BuildKit。如果您运行的Docker Engine版本早于23.0,可以通过设置环境变量或在守护进程配置中将BuildKit设置为默认选项来启用BuildKit。
DOCKER_BUILDKIT=1 docker build --file /path/to/dockerfile -t docker_image_name:tag
# syntax=docker/dockerfile:1.4
用于Dockerfile中的heredoc。
# syntax=docker/dockerfile:1.4 # heredocs所需 [3, 4]
项目目录树
├── main.py
├── requirements.txt
└── src
├── log.py
└── prometheus.py
多阶段 Dockerfile
如前所述,首先配置 base
阶段,以便在接下来的 build
和 runtime
阶段中使用。
base
阶段
- 基础镜像
ARG JFROG=jfrog.example.com
FROM ${JFROG}/docker/python:3.13-slim AS base
- 更改默认 SHELL
- 使用自定义 shell 的安全方式,带有
pipefail
和errexit
选项,这在 Debian 私有仓库设置部分的 Heredoc 中非常有用。
- 使用自定义 shell 的安全方式,带有
SHELL ["/bin/bash", "-c", "-o", "pipefail", "-o", "errexit"]
- 环境
ARG JFROG=jfrog.example.com
ENV PYTHONUNBUFFERED=1 \
PYTHONDONTWRITEBYTECODE=1 \
PIP_DISABLE_PIP_VERSION_CHECK=on \
PIP_INDEX_URL=https://${JFROG}/artifactory/api/pypi/python/simple/
-
PIP_DISABLE_PIP_VERSION_CHECK
: 使 pip 在安装依赖时检查或不检查其版本。 (on
/off
)
-
PIP_INDEX_URL
: 设置 pip 的自定义索引 URL,以便全局下载和安装。
-
- 如果私有仓库中的 PYPI 仓库结构不同,请更改
PIP_INDEX_URL
的值。
- 如果私有仓库中的 PYPI 仓库结构不同,请更改
-
- 私有 Debian 仓库(离线安装)
在 Docker 中使用 heredoc
来更改基础镜像的 apt 源,以便从私有 Debian 仓库更新和安装软件包。heredoc 需要之前提到的 dockerfile 语法。
如果私有仓库中的 Debian 仓库结构不同,请更改 URIs
。
# 使用 DEB822 格式(.sources 文件) - 适用于较新系统
RUN <<EOF
CODENAME=$(grep VERSION_CODENAME /etc/os-release | cut -d'=' -f2)
DISTRO=$(grep '^ID=' /etc/os-release | cut -d'=' -f2)
cat > /etc/apt/sources.list.d/debian.sources <<SOURCE_FILE_CONTENT
Types: deb
URIs: https://${JFROG}/artifactory/debian/debian/
Suites: ${CODENAME} ${CODENAME}-updates
Components: main
Trusted: true
Types: deb
URIs: https://${JFROG}/artifactory/debian/debian-security/
Suites: ${CODENAME}-security
Components: main
Trusted: true
SOURCE_FILE_CONTENT
EOF
- 在所有阶段安装共享和常用软件包。
- 在软件包安装时,无需安装推荐的软件包,以减少镜像大小。
- 安装后,为了减小镜像大小,需要删除软件包下载。
RUN apt-get update && apt-get install -y --no-install-recommends \
ca-certificates \
curl \
gnupg \
lsb-release \
&& rm -rf /var/lib/apt/lists/*
构建
阶段
- 使用准备好的
基础
镜像作为构建
镜像
FROM base AS build
- 在所有阶段中不需要构建特定的包,因此只需在
build
阶段安装它们。
RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential && \
rm -rf /var/lib/apt/lists/*
- 安装要求
- 切换目录到
app
。 - 在
runtime
阶段创建虚拟环境,该virtualenv
将被复制 到镜像中。 - 使用 缓存挂载 以加快构建速度。
- 为了减小镜像大小,使用 禁用 pip 缓存,使用
--no-cache-dir
标志安装依赖。
- 切换目录到
WORKDIR /app
RUN python -m venv .venv
ENV PATH="/app/.venv/bin:$PATH"
COPY requirements.txt .
RUN --mount=type=cache,target=/root/.cache/pip \
pip --timeout 100 install --no-cache-dir -r requirements.txt
runtime
阶段
-
- 使用准备好的
base
镜像作为build
镜像
- 使用准备好的
FROM base AS build
WORKDIR /app
- 安全最佳实践
- 创建
group
和user
以利用 Kubernetes 的runAsUser
、runAsGroup
和fsGroup
securityContext
- 创建
RUN addgroup --gid 1001 --system nonroot && \
adduser --no-create-home --shell /bin/false \
--disabled-password --uid 1001 --system --group nonroot
USER nonroot:nonroot
-
- 虚拟环境
-
- 将
/app/.venv/bin
添加到PATH
中。 -
将构建阶段的 复制
virtualenv
。
- 将
-
- 虚拟环境
ENV VIRTUAL_ENV=/app/.venv \
PATH="/app/.venv/bin:$PATH"
COPY --from=build --chown=nonroot:nonroot /app/.venv /app/.venv
- 复制
src
目录。
COPY --chown=nonroot:nonroot src /app/src
html
COPY –chown=nonroot:nonroot main.py .
CMD
用于从镜像运行容器。
CMD ["python", "/app/main.py"]
优化前后对比
优化前
Dockerfile如下:
FROM jfrog.example.com/docker/python:latest
WORKDIR /app
ADD src/ .
RUN pip config set global.index-url https://jfrog.example.com/artifactory/api/pypi/python/simple/ && \
pip --timeout 100 install -r requirements.txt
CMD ["python","-u","main.py"]
构建后的大小为 1.02GB
。
优化后的最终 Dockerfile
经过所有优化和多阶段 Dockerfile 后,大小减少到 242MB
。
# syntax=docker/dockerfile:1.4
ARG JFROG=jfrog.example.com
FROM ${JFROG}/docker/python:3.13-slim AS base
SHELL ["/bin/bash", "-c", "-o", "pipefail", "-o", "errexit"]
ARG JFROG=jfrog.example.com
plaintext
ENV PYTHONUNBUFFERED=1 \
PYTHONDONTWRITEBYTECODE=1 \
PIP_DISABLE_PIP_VERSION_CHECK=on \
PIP_INDEX_URL=https://${JFROG}/artifactory/api/pypi/python/simple/
# 使用 DEB822 格式 (.sources 文件) – 适用于较新系统
RUN <<EOF
CODENAME=$(grep VERSION_CODENAME /etc/os-release | cut -d’=’ -f2)
DISTRO=$(grep ‘^ID=’ /etc/os-release | cut -d’=’ -f2)
cat > /etc/apt/sources.list.d/debian.sources <<SOURCE_FILE_CONTENT
Types: deb
URIs: https://${JFROG}/artifactory/debian/debian/
Suites: ${CODENAME} ${CODENAME}-updates
Components: main
Trusted: true
Types: deb
URIs: https://${JFROG}/artifactory/debian/debian-security/
Suites: ${CODENAME}-security
Components: main
Trusted: true
SOURCE_FILE_CONTENT
EOF
RUN apt-get update && apt-get install -y –no-install-recommends \
ca-certificates \
curl \
gnupg \
lsb-release \
&& rm -rf /var/lib/apt/lists/*
FROM base AS build
RUN apt-get update && apt-get install -y –no-install-recommends \
build-essential && \
rm -rf /var/lib/apt/lists/*
WORKDIR /app
RUN python -m venv .venv
ENV PATH=”/app/.venv/bin:$PATH”
COPY requirements.txt .
RUN –mount=type=cache,target=/root/.cache/pip \
pip –timeout 100 install –no-cache-dir -r requirements.txt
FROM base AS runtime
WORKDIR /app
RUN addgroup --gid 1001 --system nonroot && \
adduser --no-create-home --shell /bin/false \
--disabled-password --uid 1001 --system --group nonroot
USER nonroot:nonroot
ENV VIRTUAL_ENV=/app/.venv \
PATH=”/app/.venv/bin:$PATH”
COPY –from=build –chown=nonroot:nonroot /app/.venv /app/.venv
COPY –chown=nonroot:nonroot src /app/src
COPY –chown=nonroot:nonroot main.py .
CMD [“python”, “/app/main.py”]