编写Python Poetry项目的Dockerfile

      ☕ 4 分钟

目标

  • Poetry和Python开箱即用,包括项目依赖,不用在启动时额外运行entrypoint.sh这类脚本初始化环境;
  • 为开发和部署都准备好镜像;
  • 使用国内镜像源以加速构建。

Dockerfile

假设你的项目是这样的结构:

.
├── Dockerfile
├── pyproject.toml
├── poetry.lock
├── scripts
└── my_awesome_ai_project

复制下列的内容到你的编辑器中,按你的用例做对应的修改。如果你需要,用法在本文的下一节。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
# syntax=docker/dockerfile:1
# 保留上面这个注释以使用 Docker BuildKit

################################
# PYTHON-BASE
# 准备好所有构建和运行时的环境变量,替换国内软件源。
################################
FROM python:3.9.17-slim as python-base

    # Python
ENV PYTHONUNBUFFERED=1 \
    # pip
    PIP_DISABLE_PIP_VERSION_CHECK=on \
    PIP_DEFAULT_TIMEOUT=100 \
    \
    # Poetry的版本
    # https://python-poetry.org/docs/configuration/#using-environment-variables
    POETRY_VERSION=1.6.1 \
    # Poetry的安装位置
    POETRY_HOME="/opt/poetry" \
    # 没有用户互动
    POETRY_NO_INTERACTION=1 \
    # 不要自动创建新的虚拟环境,我们只让Python和Poetry用待会我们自己创建好的,
    # 无论是构建还是运行时都是如此。
    POETRY_VIRTUALENVS_CREATE=false \
    \
    # 项目依赖和虚拟环境最后会放在这里
    VIRTUAL_ENV="/venv" \
    \
    # Node.js大版本,如果你不需要可以去掉
    NODE_MAJOR=18

# 让系统能够找到Poetry和VIRTUAL_ENV
ENV PATH="$POETRY_HOME/bin:$VIRTUAL_ENV/bin:$PATH"

# 手动创建虚拟环境
RUN python -m venv $VIRTUAL_ENV

# 我们的项目目录,这里我们让Python命令行在以后找依赖时考虑这个目录
WORKDIR /app
ENV PYTHONPATH="/app:$PYTHONPATH"

# Huggingface的权重缓存路径,如果你不需要可以去掉
# https://huggingface.co/docs/transformers/installation?highlight=transformers_cache#caching-models
ENV TRANSFORMERS_CACHE="/opt/transformers_cache/"

# 国内镜像源
RUN sed -i 's/deb.debian.org/mirrors.cloud.tencent.com/g' /etc/apt/sources.list.d/debian.sources && \
    pip config set global.index-url https://mirrors.cloud.tencent.com/pypi/simple

################################
# BUILDER-BASE
# 安装和编译依赖
################################
FROM python-base as builder-base
RUN apt-get update && \
    apt-get install -y \
    apt-transport-https \
    gnupg \
    ca-certificates \
    build-essential \
    git \
    nano \
    curl

# 如果你不需要Node.js,可以去掉这一段
RUN mkdir -p /etc/apt/keyrings && \
    curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg && \
    echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://mirrors.ustc.edu.cn/nodesource/deb/node_$NODE_MAJOR.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list && \
    apt-get update && apt-get install -y nodejs && \
    npm config -L global set registry http://mirrors.cloud.tencent.com/npm/

# 安装poetry - 会依赖 $POETRY_VERSION & $POETRY_HOME
# --mount 参数会提示 buildx 缓存 /root/.cache 以加速下一次构建
RUN --mount=type=cache,target=/root/.cache \
    curl -sSL https://install.python-poetry.org | python -

# 开始安装依赖,注意 /app 目录下的内容在后面其实会被覆盖掉
WORKDIR /app
COPY poetry.lock pyproject.toml ./
COPY scripts scripts/
COPY my_awesome_ai_project/ my_awesome_ai_project/

# 安装项目运行依赖到VIRTUAL_ENV目录中
RUN --mount=type=cache,target=/root/.cache \
    poetry install --no-root --only main

# 预载Huggingface模型权重,这里作为例子,按你的用例修改
RUN poetry run python scripts/bootstrap.py

# 编译C依赖,这里作为例子,按你的用例修改
RUN --mount=type=cache,target=/app/scripts/vendor \
    poetry run python scripts/build-c-denpendencies.py && \
    cp scripts/lib/*.so /usr/lib

################################
# DEVELOPMENT
# 本地开发和CI测试用的镜像
################################
FROM builder-base as development

WORKDIR /app

# 安装测试和lint所用的依赖
# 运行时依赖已经安装过了,所以这里会比较快
RUN --mount=type=cache,target=/root/.cache \
    poetry install --no-root --with test,lint

# 要暴露的端口,按你的用例修改
EXPOSE 8080
CMD ["bash"]


################################
# PRODUCTION
# 生产用的镜像
################################
FROM python-base as production

# 根证书,你可能还会想加上时区信息
RUN DEBIAN_FRONTEND=noninteractive apt-get update && \
    apt-get install -y --no-install-recommends \
    ca-certificates && \
    apt-get clean

# 拷贝已经构建好的 Poetry + venv
COPY --from=builder-base $POETRY_HOME $POETRY_HOME
COPY --from=builder-base $VIRTUAL_ENV $VIRTUAL_ENV
# 拷贝已经构建好的C依赖,这里作为例子,按你的用例修改
COPY --from=builder-base /app/scripts/lib/*.so /usr/lib
# 拷贝预载的模型权重,这里作为例子,按你的用例修改
COPY --from=builder-base $TRANSFORMERS_CACHE $TRANSFORMERS_CACHE

# 拷贝程序
WORKDIR /app
COPY poetry.lock pyproject.toml ./
COPY my_awesome_ai_project/ my_awesome_ai_project/

EXPOSE 8080
CMD ["python", "my_awesome_ai_project/app.py"]

用法

  • 构建生产用的镜像:docker build --progress plain -t registry.example.com/project/production .
  • 构建开发和测试用的镜像:docker build --progress plain -t registry.example.com/project/dev --target development .

测试用的镜像可以作为CI运行的环境,将程序挂载到容器中,这能大幅缩短CI时间。

原理

  • VIRTUAL_ENV让Python和Poetry知道我们在虚拟环境中,好让它们查找安装依赖。
  • PYTHONPATH让Python考虑你在/app中存放的,自己编写的包。
  • PATH修改使得我们可以直接调用在VIRTUAL_ENV当中安装的可执行依赖。
  • 生产和开发的镜像是同源的,它们可以复用大部分的镜像层,这提升了镜像构建和存储的效率。

参考资料


nanmu42
作者
nanmu42
用心构建美好事物。

目录