# ============================================================ # Dockerfile:项目镜像(多阶段构建) # 基础镜像:python-base:3.12.8(RockyLinux 8 + Python 3.12.8 + mariadb-connector-c) # # 设计要点 # - builder 只编译依赖,装到 /opt/deps,不碰基础镜像的 Python 目录 # - runtime 继承基础镜像的 Python,只把依赖的 site-packages COPY 过来 # - MySQL 运行库(mariadb-connector-c)由基础镜像预置,runtime 无需重复安装 # - 用 COPY --chown 指定属主,避免额外的 chown 层 # - 源码 COPY 放在依赖之后,保护依赖层缓存 # # 构建参数 # VAR_BASE_IMAGE 基础镜像 # VAR_PYTHON_PREFIX Python 安装前缀(与基础镜像一致) # VAR_PYTHON_SHORT_VERSION Python 主次版本号,用于 site-packages 路径 # VAR_PYFILE_PATH 项目源码目录 # VAR_PROFILES 应用 profile(dev / prod) # VAR_PORT 应用监听端口 # VAR_PYFILE 入口文件名(不含 .py) # ============================================================ # ------------------------------------------------------------ # 全局构建参数 # 在 FROM 之前声明,可供后续各阶段引用 # ------------------------------------------------------------ ARG VAR_BASE_IMAGE ARG VAR_PYTHON_PREFIX=/usr/local/python3.12.8 ARG VAR_PYTHON_SHORT_VERSION=3.12 # ============================================================ # 阶段一:builder —— 编译并安装项目依赖 # ============================================================ FROM ${VAR_BASE_IMAGE} AS builder # 阶段内重新声明,否则读不到全局 ARG # Docker 规则:全局 ARG 只在 FROM 行可见,阶段内使用必须重新声明 ARG VAR_PYTHON_PREFIX ARG VAR_PYTHON_SHORT_VERSION ARG VAR_PYFILE_PATH # 基础镜像末尾是 esxi 用户;dnf/pip 需要写系统目录和 /opt/deps, # 所以这里仍需 root USER root LABEL MAINTAINER="djangoadmin@126.com" # 编译阶段环境变量 # 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 必需) RUN 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; \ dnf clean all; rm -rf /var/cache/dnf # 配置 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 服务器,单独安装 RUN 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_PORT ARG VAR_PYFILE ARG VAR_PYFILE_PATH LABEL MAINTAINER="djangoadmin@126.com" # 运行期环境变量 ENV PYTHONUNBUFFERED=1 \ PYTHONDONTWRITEBYTECODE=1 # ARG 转 ENV,运行期可读取 # PORT 供 EXPOSE 与 CMD 使用 # PYFILE 入口文件名 # PROFILES 应用读取的 profile ENV PORT=${VAR_PORT} \ PYFILE=${VAR_PYFILE} \ PROFILES=${VAR_PROFILES} # 把 builder 编译好的 site-packages 和可执行文件拷到基础镜像 Python 对应目录 # 目标路径必须与基础镜像 Python 的实际 site-packages 一致 # 用 COPY --chown 指定属主,避免额外的 chown 层 COPY --chown=esxi:esxi --from=builder \ /opt/deps/lib/python${VAR_PYTHON_SHORT_VERSION}/site-packages/ \ ${VAR_PYTHON_PREFIX}/lib/python${VAR_PYTHON_SHORT_VERSION}/site-packages/ COPY --chown=esxi:esxi --from=builder /opt/deps/bin/ ${VAR_PYTHON_PREFIX}/bin/ WORKDIR /opt/apps # 拷项目源码(放在依赖之后,保护 pip 层缓存) # 建议配合 .dockerignore 排除 .git、__pycache__、本地虚拟环境等 COPY --chown=esxi:esxi ${VAR_PYFILE_PATH}/ ./ USER esxi EXPOSE ${PORT} # 启动命令 # ${PYFILE} 为入口文件名(不含 .py),如 manage # runserver 仅适合开发/调试;生产环境建议改用 uwsgi / gunicorn,例如: # CMD ["sh","-c","uwsgi --http 0.0.0.0:${PORT} \ # --module 你的项目名.wsgi:application \ # --processes 4 --threads 2"] CMD ["sh", "-c", "gunicorn -c gunicorn.conf.py src.wsgi:app -b 0.0.0.0:${PORT}"]