离线多阶段Python Dockerfile

需要减少一个Python应用程序的Docker镜像大小,同时必须支持离线安装,无论是在PYPI包安装中还是在APT更新和安装包中。

让我们逐行进行优化。

Docker构建 buildx 语法

  • 首先确保Docker使用 docker buildx 作为其默认的 docker build

BuildKit

如果您已安装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 # heredocs所需 [3, 4]

项目目录树

├── main.py
├── requirements.txt
└── src
    ├── log.py
    └── prometheus.py

 

多阶段 Dockerfile

如前所述,首先配置 base 阶段,以便在接下来的 buildruntime 阶段中使用。

base 阶段

  • 基础镜像
ARG JFROG=jfrog.example.com

FROM ${JFROG}/docker/python:3.13-slim AS base
  • 更改默认 SHELL
    • 使用自定义 shell 的安全方式,带有 pipefailerrexit 选项,这在 Debian 私有仓库设置部分的 Heredoc 中非常有用。
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 的值。
    • 私有 Debian 仓库(离线安装)

 

在 Docker 中使用 heredoc 来更改基础镜像的 apt 源,以便从私有 Debian 仓库更新和安装软件包。heredoc 需要之前提到的 dockerfile 语法
如果私有仓库中的 Debian 仓库结构不同,请更改 URIs

DEB822 格式(apt .sources 文件)

# 使用 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/*

 

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
  • 安全最佳实践
    • 创建 groupuser 以利用 Kubernetes 的 runAsUserrunAsGroupfsGroup securityContext

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
  • 复制 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”]


更多