This commit is contained in:
2026-09-12 08:34:28 +08:00
parent 957be9079d
commit 8593e3a215
+121 -116
View File
@@ -1,154 +1,159 @@
# syntax=docker/dockerfile:1.7
# ============================================================ # ============================================================
# Dockerfile:单阶段构建 Python 应用镜像(RockyLinux 8 + Python 3.12.8) # Dockerfile:项目镜像(多阶段构建)
# 基础镜像:python-base:3.12.8(RockyLinux 8 + Python 3.12.8 + mariadb-connector-c)
# #
# 设计思路: # 设计要点
# - 单阶段构建,所有依赖一次装齐,避免多阶段"包找不到/库缺失"问题 # - builder 只编译依赖,装到 /opt/deps,不碰基础镜像的 Python 目录
# - 编译工具链和运行库都保留在最终镜像里,换取构建稳定性和排错简单 # - runtime 继承基础镜像的 Python,只把依赖的 site-packages COPY 过来
# - 通过 --build-arg 动态注入变量,便于 CI 复用同一份 Dockerfile # - MySQL 运行库(mariadb-connector-c)由基础镜像预置,runtime 无需重复安装
# - 源码 COPY 放在 pip install 之后,保护依赖层缓存
# - 使用 BuildKit cache mount 加速 dnf / pip
# #
# 构建参数说明: # 构建参数
# VAR_BASE_IMAGE 基础镜像(RockyLinux 8 + Python 3.12.8 + esxi 用户) # VAR_BASE_IMAGE 基础镜像
# VAR_PROFILES 应用 profile(dev / prod) # VAR_PYTHON_PREFIX Python 安装前缀(与基础镜像一致)
# VAR_PORT 应用监听端口 # VAR_PYTHON_SHORT_VERSION Python 主次版本号,用于 site-packages 路径
# VAR_PYFILE 启动入口文件名(不含 .py),如 manage # VAR_PYFILE_PATH 项目源码目录
# VAR_PYFILE_PATH 项目源码目录(相对构建上下文) # VAR_PROFILES 应用 profile(dev / prod)
# VAR_PORT 应用监听端口
# VAR_PYFILE 入口文件名(不含 .py)
# ============================================================ # ============================================================
# ------------------------------------------------------------ # ------------------------------------------------------------
# 声明构建参数 # 全局构建参数
# 在 FROM 之前声明,可供后续各阶段引用
# ------------------------------------------------------------ # ------------------------------------------------------------
ARG VAR_BASE_IMAGE ARG VAR_BASE_IMAGE
ARG VAR_PYTHON_PREFIX=/usr/local/python3.12.8
ARG VAR_PYTHON_SHORT_VERSION=3.12
# ------------------------------------------------------------
# 基础镜像
# ------------------------------------------------------------
FROM ${VAR_BASE_IMAGE}
# 镜像作者信息 # ============================================================
LABEL MAINTAINER="djangoadmin@163.com" # 阶段一:builder —— 编译并安装项目依赖
# ============================================================
FROM ${VAR_BASE_IMAGE} AS builder
# ------------------------------------------------------------ # 阶段内重新声明,否则读不到全局 ARG
# 切回 root # Docker 规则:全局 ARG 只在 FROM 行可见,阶段内使用必须重新声明
# 基础镜像末尾通常设置了非 root 用户(如 esxi), ARG VAR_PYTHON_PREFIX
# 不切回 root,后面的 dnf install / mkdir / chown 都会因权限不足失败 ARG VAR_PYTHON_SHORT_VERSION
# ------------------------------------------------------------ ARG VAR_PYFILE_PATH
# 基础镜像末尾是 esxi 用户,装包需切回 root
USER root USER root
# ------------------------------------------------------------ LABEL MAINTAINER="djangoadmin@126.com"
# Python 运行期环境变量
# PYTHONUNBUFFERED=1 Python 输出不缓冲,日志实时可见
# PYTHONDONTWRITEBYTECODE=1 不生成 .pyc,避免容器内产生垃圾文件
# ------------------------------------------------------------
ENV PYTHONUNBUFFERED=1 \
PYTHONDONTWRITEBYTECODE=1
# ------------------------------------------------------------ # 编译阶段环境变量
# 声明运行期构建参数 # PYTHONUNBUFFERED=1 日志实时输出
# ------------------------------------------------------------ # PYTHONDONTWRITEBYTECODE=1 不生成 .pyc
# PIP_NO_CACHE_DIR=1 不缓存 wheel
# PIP_DISABLE_PIP_VERSION_CHECK=1 关闭 pip 版本检查
ENV PYTHONUNBUFFERED=1 \
PYTHONDONTWRITEBYTECODE=1 \
PIP_NO_CACHE_DIR=1 \
PIP_DISABLE_PIP_VERSION_CHECK=1
# 安装编译工具链
# gcc / gcc-c++ / make 编译 C 扩展(mysqlclient、uwsgi 等)
# openssl-devel cryptography、_ssl 等
# bzip2-devel / libffi-devel
# mariadb-devel 编译 mysqlclient(提供 .pc 文件)
# pkgconf-pkg-config 提供 pkg-config 命令(mysqlclient 2.2.x 必需)
# 使用 BuildKit cache mount,重复构建不再重新下载 RPM
RUN --mount=type=cache,target=/var/cache/dnf,sharing=locked,id=dnf-cache \
set -eux; \
# 替换 dnf 源为阿里云镜像
sed -e 's|^mirrorlist=|#mirrorlist=|g' \
-e 's|^#baseurl=http://dl.rockylinux.org/$contentdir|baseurl=https://mirrors.aliyun.com/rockylinux|g' \
-i.bak /etc/yum.repos.d/*.repo; \
dnf install -y --setopt=install_weak_deps=False \
gcc gcc-c++ make \
openssl-devel bzip2-devel libffi-devel \
mariadb-devel pkgconf-pkg-config
# 配置 pip 国内镜像源(阿里云 PyPI)
RUN pip3 config set global.index-url http://mirrors.aliyun.com/pypi/simple && \
pip3 config set install.trusted-host mirrors.aliyun.com
# 只拷 requirements.txt
# 源码变化但依赖未变时,可复用 pip install 层缓存
WORKDIR /opt/apps
COPY ${VAR_PYFILE_PATH}/requirements.txt ./
# 依赖装到 /opt/deps,与基础镜像 Python 目录解耦
# --prefix=/opt/deps 安装到独立目录,runtime 只 COPY 这个目录的产物
# uwsgi 生产环境 WSGI 服务器,单独安装
# 使用 pip cache mount,重复构建不再重新下载 wheel
RUN --mount=type=cache,target=/root/.cache/pip,sharing=locked,id=pip-cache \
python3 -m pip install --prefix=/opt/deps -r requirements.txt && \
python3 -m pip install --prefix=/opt/deps uwsgi
# ============================================================
# 阶段二:runtime —— 最终业务镜像
# ============================================================
FROM ${VAR_BASE_IMAGE} AS runtime
# 阶段内重新声明
ARG VAR_PYTHON_PREFIX
ARG VAR_PYTHON_SHORT_VERSION
ARG VAR_PROFILES ARG VAR_PROFILES
ARG VAR_PORT ARG VAR_PORT
ARG VAR_PYFILE ARG VAR_PYFILE
ARG VAR_PYFILE_PATH ARG VAR_PYFILE_PATH
# ------------------------------------------------------------ USER root
# 将 ARG 转为 ENV,运行期可通过环境变量读取
# ------------------------------------------------------------ LABEL MAINTAINER="djangoadmin@126.com"
# 运行期环境变量
ENV PYTHONUNBUFFERED=1 \
PYTHONDONTWRITEBYTECODE=1
# ARG 转 ENV,运行期可读取
# PORT 供 EXPOSE 与 CMD 使用
# PYFILE 入口文件名
# PROFILES 应用读取的 profile
ENV PORT=${VAR_PORT} \ ENV PORT=${VAR_PORT} \
PYFILE=${VAR_PYFILE} \ PYFILE=${VAR_PYFILE} \
PROFILES=${VAR_PROFILES} PROFILES=${VAR_PROFILES}
# ------------------------------------------------------------ # 运行期依赖
# 安装系统依赖(dnf 源换成国内镜像) # mariadb-connector-c 基础镜像已预置,无需重复安装
# # net-tools 按需保留,如不需要可删
# RockyLinux 8 官方源可用,但换阿里云镜像更快: RUN --mount=type=cache,target=/var/cache/dnf,sharing=locked,id=dnf-cache \
# baseurl=https://mirrors.aliyun.com/rockylinux set -eux; \
#
# 编译期工具:
# gcc / gcc-c++ C/C++ 编译器
# make / cmake 构建工具
# openssl-devel 编译 _ssl / cryptography
# bzip2-devel 编译 _bz2
# libffi-devel 编译 _ctypes
# mariadb-devel 编译 mysqlclient(提供 .pc 文件)
# pkgconf-pkg-config 提供 pkg-config 命令(mysqlclient 2.2.x 必需)
#
# 运行期库:
# net-tools netstat 等网络排查工具
# openssl-libs SSL 运行库
# bzip2-libs bz2 运行库
# libffi ctypes 运行库
# mariadb-connector-c MySQL/MariaDB 客户端运行库
#
# 注意:
# 1) RockyLinux 8 里 MySQL 实际由 MariaDB 提供,
# 运行库包名为 mariadb-connector-c(不是 mysql-libs)
# 2) mariadb-devel 和 mysql-devel 不要同时安装,会冲突
# 3) 末尾清理 dnf 缓存,避免缓存进入镜像层
# ------------------------------------------------------------
RUN set -eux; \
# ---- 把 RockyLinux 8 官方源替换为阿里云镜像 ----
sed -e 's|^mirrorlist=|#mirrorlist=|g' \ sed -e 's|^mirrorlist=|#mirrorlist=|g' \
-e 's|^#baseurl=http://dl.rockylinux.org/$contentdir|baseurl=https://mirrors.aliyun.com/rockylinux|g' \ -e 's|^#baseurl=http://dl.rockylinux.org/$contentdir|baseurl=https://mirrors.aliyun.com/rockylinux|g' \
-i.bak /etc/yum.repos.d/*.repo; \ -i.bak /etc/yum.repos.d/*.repo; \
# ---- 安装编译工具链 + 运行库 ---- dnf install -y --setopt=install_weak_deps=False net-tools
dnf install -y --setopt=install_weak_deps=False \
yum-utils net-tools \
gcc gcc-c++ make cmake \
openssl-devel bzip2-devel libffi-devel \
mariadb-devel pkgconf-pkg-config \
openssl-libs bzip2-libs libffi mariadb-connector-c; \
# ---- 清理 dnf 缓存,减小镜像体积 ----
dnf clean all; rm -rf /var/cache/dnf
# ------------------------------------------------------------ # 把 builder 编译好的 site-packages 和可执行文件拷到基础镜像 Python 对应目录
# 配置 pip 国内镜像源(阿里云 PyPI) # 目标路径必须与基础镜像 Python 的实际 site-packages 一致
# ------------------------------------------------------------ # 用 VAR_PYTHON_SHORT_VERSION 拼接,避免硬编码 python3.12
RUN pip3 config set global.index-url http://mirrors.aliyun.com/pypi/simple && \ COPY --from=builder /opt/deps/lib/python${VAR_PYTHON_SHORT_VERSION}/site-packages/ \
pip3 config set install.trusted-host mirrors.aliyun.com ${VAR_PYTHON_PREFIX}/lib/python${VAR_PYTHON_SHORT_VERSION}/site-packages/
COPY --from=builder /opt/deps/bin/ ${VAR_PYTHON_PREFIX}/bin/
# ------------------------------------------------------------
# 升级 pip / setuptools / wheel
# --no-cache-dir 避免 pip 缓存进入镜像层
# ------------------------------------------------------------
RUN python3 -m pip install --upgrade --no-cache-dir pip setuptools wheel
# ------------------------------------------------------------
# 创建工作区
# ------------------------------------------------------------
RUN mkdir -p /opt/apps
WORKDIR /opt/apps WORKDIR /opt/apps
# ------------------------------------------------------------ # 拷项目源码(放在依赖之后,保护 pip 层缓存)
# 拷贝项目源码 # 建议配合 .dockerignore 排除 .git、__pycache__、本地虚拟环境等
# ------------------------------------------------------------
COPY ${VAR_PYFILE_PATH}/ ./ COPY ${VAR_PYFILE_PATH}/ ./
# ------------------------------------------------------------ # 修正属主,保证 esxi 可读写源码和 Python 目录
# 安装 Python 依赖 RUN chown -R esxi:esxi /opt/apps ${VAR_PYTHON_PREFIX}
# -r requirements.txt 项目依赖
# uwsgi 生产环境 WSGI 服务器
# ------------------------------------------------------------
RUN python3 -m pip install --no-cache-dir -r requirements.txt && \
python3 -m pip install --no-cache-dir uwsgi
# ------------------------------------------------------------
# 修正属主
# ------------------------------------------------------------
RUN chown -R esxi:esxi /opt/apps
# ------------------------------------------------------------
# 切回非 root 用户运行应用
# ------------------------------------------------------------
USER esxi USER esxi
# ------------------------------------------------------------
# 声明暴露端口
# ------------------------------------------------------------
EXPOSE ${PORT} EXPOSE ${PORT}
# ------------------------------------------------------------
# 启动命令 # 启动命令
# ${PYFILE} 为入口文件名(不含 .py),如 manage # ${PYFILE} 为入口文件名(不含 .py),如 manage
# 生产环境建议改用 uwsgi # runserver 仅适合开发/调试;生产环境建议改用 uwsgi / gunicorn,例如:
# ------------------------------------------------------------ # CMD ["sh","-c","uwsgi --http 0.0.0.0:${PORT} \
CMD ["sh", "-c", "python3 manage.py runserver 0.0.0.0:${PORT} --noreload"] # --module 你的项目名.wsgi:application \
# --processes 4 --threads 2"]
CMD ["sh", "-c", "python3 ${PYFILE}.py runserver 0.0.0.0:${PORT} --noreload"]