写在前面
本文以部署本地开发环境,以实现快速部署为基准、方便开发为前提做说明
背景
本人在实际工作过程中,使用 hyperf 框架开发时遇到这种情况:
- 同时开发多个项目
- 同时使用不同的 hyperf 框架版(2.x、3.x)
- 不同项目对接不同的数据库(Mysql、Oracle、Sqlserver)
遇到的问题有很多,比如:
- 2.x 和 3.x 对 php 版本和 swoole 版本不一样
- 每个 php 版本需要单独安装对应的 扩展
- 不同的 php 版本,composer 需要重新安装
- 项目中使用 composer 添加扩展包时,还要对应自己的项目版本使用正确的 composer
- ... ... ...
这一套下来,耗费时间长,如果换台设备,重新部署也很麻烦;
所以,考虑到使用容器开发来解决当下的困局 ... ... ...
快速跳转
一、准备工作
环境准备
基于本人开发环境做说明
- 一个 linux 环境、已安装好 docker、docker-compose
- CentOS 版本:
CentOS Linux release 7.9.2009 (Core) - docker 版本:
Docker version 24.0.5, build ced0996 - docker-compos 版本:
Docker Compose version v2.20.2
- CentOS 版本:
二、开始部署(基础)
本文以 php7.4 + hyperf2.x 做开发环境的说明
准备 Dockerfile
FROM php:7.4-cli-bullseye
# 设置时区
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
# 更新并安装基础依赖
RUN apt-get update && apt-get install -y \
--no-install-recommends libfreetype6-dev libjpeg62-turbo-dev libpng-dev libzip-dev \
&& apt-get clean && rm -rf /var/lib/apt/lists/*
# 安装 redis 和 swoole 扩展
RUN pecl install redis-5.3.7 swoole-4.8.13
# 移动 php.ini 配置文件
RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini"
# 启用扩展
RUN docker-php-ext-enable redis swoole
# 配置 GD 扩展
RUN docker-php-ext-configure gd --with-jpeg=/usr/include --with-freetype=/usr/include/freetype2/
# 安装 PHP 扩展
RUN docker-php-ext-install -j$(nproc) pcntl gd zip pdo_mysql opcache
# 添加自定义 PHP 配置
RUN echo "swoole.use_shortname='Off'" >> /usr/local/etc/php/conf.d/default.ini \
&& echo "memory_limit=1G" >> /usr/local/etc/php/conf.d/default.ini \
&& echo "opcache.enable_cli='on'" >> /usr/local/etc/php/conf.d/default.ini
# 安装 Composer
ENV COMPOSER_HOME /root/composer
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
ENV PATH $COMPOSER_HOME/vendor/bin:$PATH
# 指定工作目录,如果使用docker exec进入容器时,默认目录就是指定的工作目录,如/data
WORKDIR /opt/www
针对这份文档,做一些小说明:
- 基础镜像使用
-bullseye版本:有些基础依赖在其他版本已经不做维护了,需要自己设置对应的源地址,比较麻烦,而该版本基于最新的 debain 做兼容的 - 该文件没有设置启动命令:是为了给多个项目自己的启动命令留空间
构建新镜像
# 执行构建
docker build -t 镜像名称:标签 .
# 检查新镜像
docker images
容器编排
docker-compose.yml
version: "3.9"
services:
php:
image: php-hyperf:7.4-cli-bullseye
volumes:
- /etc/localtime:/etc/localtime
- /www:/opt/www
restart: always
container_name: hyperf01
# networks:
# - php-net
# ports:
# - 9505:9501
network_mode: "host"
tty: true
stdin_open: true
# command: ["php", "bin/hyperf.php", "start"]
#networks:
# php-net:
# external:
# name: php-net
针对这份文档,做一些小说明:
一般情况下,使用 docker-compose 编排容器是这样的:
version: "3.9" services: php: image: php-hyperf:7.4-cli-bullseye volumes: - /etc/localtime:/etc/localtime - /www/hyperf01:/opt/www/hyperf01 restart: always container_name: hyperf01 networks: - php-net ports: - 9505:9501 command: ["php", "bin/hyperf.php", "start"] networks: php-net: external: name: php-net
image: Dockerfile 构建出来的镜像名volumes: 挂载目录- 开发环境目录结构解释:/www 是项目目录,里面包含多个项目文件夹
- 此处挂载 /www ,是为了方便只使用一个 php 容器,可以运行多个 hyperf 项目
command: 容器启动时的执行命令- 一般情况下,我们挂载了指定的项目名称,在容器启动时需要指定启动命令
- 但是,目前挂载的 /www,没有指定的项目,如果使用
command: ["php", "bin/hyperf.php", "start"]容器会启动不成功 - 所以,我们在这里不能使用 command 启动命令,也是为了方便多项目各自启动
tty、stdin_open:- Dockerfile、docker-compose 中都没有了启动命令、容器启动不成功
- 所以,追加这两个参数,允许容器可以成功启动,容器不退出
ports: 端口映射- 为了能直接使用每个项目自己配置的端口
- 因为为了方便扩展(后续追加更多的项目)
- 每个项目都有自己的端口号,如果在这里设置,那么每增加一个项目,就需要修改一次端口映射,并重新编排容器,很不方便
- 所以就取消了这里的端口映射
network_mode: 网络模式- 固定值:
host,容器共享宿主机网络,可以实现不用配置端口号 - 与
ports参数不共存 - 使用了共享网络模式,那么就不再需要配置新的网络了,就取消了
networks相关的参数配置
- 固定值:
执行容器编排
# 后台运行,创建容器并启动
docker-compose up -d
三、启动并运行项目
当我们启动好容器后、进入php容器中
docker exec -it hyperf01 bash
此时可以看到我们的项目目录 /opt/www ,因为这里包含了所有的项目目录,找到你自己的使用 php7.x 的项目,启动即可,启动成功后可以看到端口号就是你再项目中配置的端口号了。
至此,便可以愉快的开启你的牛马生活了 ... ... ...
四、提升
前面,我们提供了一个完整的可直接享用的 Dockerfile 文件,虽然是方便食用,但是问题也随之出现了:
- 当前是 php7.x 如果我们要换成 php8.x 怎么办呢?对应的 swoole 扩展版本 也需要修改
- 如果我们需要追加其他扩展怎么办呢?
- ... ... ...
如果按照当前的模式,每次处理这些问题,都需要去修改 Dockerfile 文件,这样就搞得很麻烦?有没有一个简单有效的办法呢???
有、有、有,,,那就是把这些可变信息,提炼成配置文件,Dockerfile 使用读配置的方式进行,那么我们就不用频繁的修改 Dockerfile 文件了,每次有变动,那就直接修改配置文件,简单直接、方便快速!!!
那么,我现在提供一套可开箱即用的配置方案,至于中间的踩坑过程就直接略过,当然,方案实现不止这么一种,大家可以自行发掘 ... ... ...
目录结构
我们以 project 作为根目录做说明
project
|--config
|--build.conf
|--logs
|--pecl
|--source
|--alpine_source.list
|--debian_source.list
|--build.sh
|--Dockerfile.base
|--Dockerfile.final
project:项目根目录project/config:构建配置文件目录project/config/build.conf:构建配置文件,方便修改构建信息project/logs:构建时产生的日志文件目录project/pecl:本地下载扩展目录,方便从本地安装扩展,例如:swoole-5.1.3.tarproject/source:自定义 alpine、debian 配置源目录project/source/alpine_source.list:自定义 alpine 系统源配置信息project/source/debian_source.list:自定义 debian 系统源配置信息project/build.sh:构建执行脚本 需要有执行权限project/Dockerfile.base:用于构建基础镜像,只做 apline、debian 系统依赖和工具等更新project/Dockerfile.final:用于构建最终镜像,分阶段:一阶段主要功能 --- 安装扩展;二阶段主要功能 --- 构建最终镜像
文件内容及说明
build.conf
# =============================================================================
# PHP Docker 构建配置文件
# 说明:所有构建参数通过此配置文件控制,无需修改 Dockerfile
# =============================================================================
# -----------------------------------------------------------------------------
# 1. PHP 基础镜像配置
# 说明:取消注释需要使用的版本,只能选择一个
# 支持:php:7.4-cli, php:7.4-alpine, php:8.2-cli, php:8.2-alpine, php:8.3-cli 等
# -----------------------------------------------------------------------------
[base_image]
php:8.2-cli
#php:8.2-alpine
#php:7.4-cli
#php:7.4-alpine
#php:8.3-cli
#php:8.3-alpine
# -----------------------------------------------------------------------------
# 2. PECL 扩展配置
# 说明:通过 pecl install 安装的扩展
# 格式:扩展名-版本号 或 扩展名(自动安装最新兼容版本)
# 示例:redis-5.3.7 或 redis
# -----------------------------------------------------------------------------
[pecl]
#redis-5.3.7
#swoole-4.8.13
#mongodb
#imagick
swoole-5.1.3
redis-6.3.0
#xdebug-3.2.0
# -----------------------------------------------------------------------------
# 3. 本地扩展配置(PHP 内置扩展)
# 说明:通过 docker-php-ext-install 安装
# 常见扩展:pcntl, gd, zip, pdo_mysql, opcache, mysqli, bcmath, sockets 等
# -----------------------------------------------------------------------------
[local]
pcntl
gd
zip
pdo_mysql
mysqli
opcache
bcmath
sockets
#pdo_pgsql
#pdo_sqlsrv
#pdo_oci
# -----------------------------------------------------------------------------
# 4. 数据库扩展配置
# 说明:特殊数据库扩展的安装配置
# 支持:pgsql, sqlsrv, oci8
# 警告:PostgreSQL 扩展会安装大型开发包,可能导致磁盘空间不足
# 建议:如果不需要 PostgreSQL,请注释掉 pgsql 行
# -----------------------------------------------------------------------------
[database]
#pgsql
#sqlsrv
#oci8
# -----------------------------------------------------------------------------
# 5. 系统工具配置
# 说明:需要安装的系统工具
# 常见工具:net-tools, procps, iputils-ping, lsof, ss, htop, vim, nano 等
# -----------------------------------------------------------------------------
[tools]
vim
ss
lsof
iputils-ping
#net-tools
#procps
#htop
#nano
#telnet
# -----------------------------------------------------------------------------
# 6. 构建模式配置
# 说明:prod 生产模式会清理编译工具优化镜像大小,dev 开发模式保留编译工具
# 选项:prod / dev
# -----------------------------------------------------------------------------
[mode]
dev
# -----------------------------------------------------------------------------
# 7. 自定义依赖源配置
# 说明:是否使用自定义的软件包源(国内镜像加速)
# 选项:true / false
# 注意:设置为 true 时,会使用 source 目录下的源配置文件
# -----------------------------------------------------------------------------
[custom_source]
false
# -----------------------------------------------------------------------------
# 8. Composer 镜像源配置
# 说明:Composer 软件包下载源
# 选项:auto(自动,推荐)/ aliyun / tencent / ustc / packagist(官方)
# -----------------------------------------------------------------------------
[composer_mirror]
auto
# -----------------------------------------------------------------------------
# 9. PHP.ini 自定义配置
# 说明:自定义 PHP 运行时配置
# 格式:配置项=值(每行一个配置)
# -----------------------------------------------------------------------------
[php]
swoole.use_shortname=Off
memory_limit=1G
upload_max_filesize=128M
post_max_size=128M
max_execution_time=300
opcache.enable=1
opcache.enable_cli=1
opcache.memory_consumption=256
opcache.interned_strings_buffer=16
opcache.max_accelerated_files=10000
# -----------------------------------------------------------------------------
# 10. 构建目标配置
# 说明:控制构建哪个阶段的镜像
# 选项:base(仅基础镜像)/ final(仅最终镜像)/ all(全部)
# -----------------------------------------------------------------------------
[build_target]
all
#final
# -----------------------------------------------------------------------------
# 11. 强制重建基础镜像配置
# 说明:是否强制重新构建基础镜像(即使已存在)
# 选项:true / false
# -----------------------------------------------------------------------------
[force_rebuild_base]
false
#true
# -----------------------------------------------------------------------------
# 12. 工作目录配置
# 说明:容器内的默认工作目录
# 默认:/var/www/html
# -----------------------------------------------------------------------------
[workdir]
/opt/www
# -----------------------------------------------------------------------------
# 13. 健康检查配置
# 说明:Docker 容器健康检查命令
# 格式:CMD-SHELL 命令
# -----------------------------------------------------------------------------
[healthcheck]
#php -v && php -m
# -----------------------------------------------------------------------------
# 14. 环境变量配置
# 说明:设置容器运行时环境变量
# 格式:KEY=VALUE(每行一个)
# -----------------------------------------------------------------------------
[env]
TZ=Asia/Shanghai
#LANG=C.UTF-8
#APP_ENV=production
#LOG_LEVEL=info
alpine_source.list
# 阿里云源
https://mirrors.aliyun.com/alpine/v3.18/main
https://mirrors.aliyun.com/alpine/v3.18/community
# 腾讯云源
# https://mirrors.cloud.tencent.com/alpine/v3.18/main
# https://mirrors.cloud.tencent.com/alpine/v3.18/community
# 清华源
# https://mirrors.tuna.tsinghua.edu.cn/alpine/v3.18/main
# https://mirrors.tuna.tsinghua.edu.cn/alpine/v3.18/community
# 官方源(备用)
# https://dl-cdn.alpinelinux.org/alpine/v3.18/main
# https://dl-cdn.alpinelinux.org/alpine/v3.18/community
debian_source.list
# 阿里云源
deb http://mirrors.aliyun.com/debian/ bullseye main non-free contrib
deb http://mirrors.aliyun.com/debian-security/ bullseye-security main
deb http://mirrors.aliyun.com/debian/ bullseye-updates main non-free contrib
deb http://mirrors.aliyun.com/debian/ bullseye-backports main non-free contrib
# 腾讯云源
# deb http://mirrors.cloud.tencent.com/debian/ bullseye main non-free contrib
# deb http://mirrors.cloud.tencent.com/debian-security/ bullseye-security main
# deb http://mirrors.cloud.tencent.com/debian/ bullseye-updates main non-free contrib
# deb http://mirrors.cloud.tencent.com/debian/ bullseye-backports main non-free contrib
# 清华源
# deb https://mirrors.tuna.tsinghua.edu.cn/debian/ bullseye main contrib non-free
# deb https://mirrors.tuna.tsinghua.edu.cn/debian/ bullseye-updates main contrib non-free
# deb https://mirrors.tuna.tsinghua.edu.cn/debian/ bullseye-backports main contrib non-free
# deb https://mirrors.tuna.tsinghua.edu.cn/debian-security bullseye-security main contrib non-free
# 官方源(备用)
# deb http://deb.debian.org/debian bullseye main contrib non-free
# deb http://deb.debian.org/debian-security/ bullseye-security main contrib non-free
# deb http://deb.debian.org/debian bullseye-updates main contrib non-free
Dockerfile.base
# =============================================================================
# PHP 基础镜像 Dockerfile
# 功能:安装系统依赖、编译工具和常用库,为后续扩展安装做准备
# 说明:此文件不需要手动修改,所有配置通过 build.conf 传入
# =============================================================================
# 构建参数:基础镜像
ARG BASE_IMAGE
FROM ${BASE_IMAGE}
# 构建参数:系统类型和配置
ARG SYSTEM_TYPE
ARG CUSTOM_SOURCE
ARG TOOLS=""
ARG DATABASE_EXTENSIONS=""
# 设置工作目录
WORKDIR /tmp/build
RUN mkdir -p /tmp/pecl
# 设置环境变量防止 OOM 和构建优化
ENV MAKEFLAGS="-j1"
ENV COMPOSER_MEMORY_LIMIT=-1
ENV DEBIAN_FRONTEND=noninteractive
# 复制源配置文件
COPY source/${SYSTEM_TYPE}_source.list /tmp/source.list
# 安装基础依赖和编译工具
RUN set -eux; \
echo "========================================"; \
echo "开始构建 PHP 基础镜像"; \
echo "系统类型: ${SYSTEM_TYPE}"; \
echo "自定义源: ${CUSTOM_SOURCE}"; \
echo "系统工具: ${TOOLS}"; \
echo "数据库扩展: ${DATABASE_EXTENSIONS}"; \
echo "========================================"; \
\
# 定义重试函数
retry_command() { \
local max_attempts=3; \
local attempt=1; \
local cmd="$@"; \
while [ $attempt -le $max_attempts ]; do \
echo "[尝试 ${attempt}/${max_attempts}] 执行: ${cmd}"; \
if eval "$cmd"; then \
echo "[成功] 命令执行成功"; \
return 0; \
fi; \
echo "[失败] 命令执行失败,等待 5 秒后重试..."; \
sleep 5; \
attempt=$((attempt + 1)); \
done; \
echo "[错误] 命令执行失败,已达到最大重试次数"; \
return 1; \
}; \
\
# Alpine 系统配置
if [ "$SYSTEM_TYPE" = "alpine" ]; then \
echo "----------------------------------------"; \
echo "[步骤 1/10] 配置 Alpine 软件源"; \
echo "----------------------------------------"; \
if [ "$CUSTOM_SOURCE" = "true" ]; then \
echo "使用自定义镜像源"; \
cat /tmp/source.list > /etc/apk/repositories; \
cat /etc/apk/repositories; \
fi; \
\
echo "----------------------------------------"; \
echo "[步骤 2/10] 更新软件包索引"; \
echo "----------------------------------------"; \
retry_command "apk update" || exit 1; \
\
echo "----------------------------------------"; \
echo "[步骤 3/10] 安装基础工具"; \
echo "----------------------------------------"; \
retry_command "apk add --no-cache bash curl wget git vim unzip" || exit 1; \
\
echo "----------------------------------------"; \
echo "[步骤 4/10] 安装开发库"; \
echo "----------------------------------------"; \
retry_command "apk add --no-cache libzip-dev zlib-dev libpng-dev libjpeg-turbo-dev freetype-dev libwebp-dev libxpm-dev openssl-dev curl-dev" || exit 1; \
\
echo "----------------------------------------"; \
echo "[步骤 5/10] 安装 XML 和基础数据库库"; \
echo "----------------------------------------"; \
retry_command "apk add --no-cache libxml2-dev oniguruma-dev sqlite-dev" || exit 1; \
\
echo "----------------------------------------"; \
echo "[步骤 6/10] 安装编译工具"; \
echo "----------------------------------------"; \
retry_command "apk add --no-cache autoconf make g++ linux-headers" || exit 1; \
\
echo "----------------------------------------"; \
echo "[步骤 7/10] 安装数据库扩展依赖"; \
echo "----------------------------------------"; \
if echo "$DATABASE_EXTENSIONS" | grep -q "pgsql"; then \
echo "安装 PostgreSQL 客户端库..."; \
retry_command "apk add --no-cache postgresql-dev postgresql-client" || echo "PostgreSQL 依赖安装失败"; \
fi; \
if echo "$DATABASE_EXTENSIONS" | grep -q "sqlsrv"; then \
echo "安装 SQL Server 依赖..."; \
retry_command "apk add --no-cache unixodbc-dev freetds-dev" || echo "SQL Server 依赖安装失败"; \
fi; \
if echo "$DATABASE_EXTENSIONS" | grep -q "oci8"; then \
echo "Oracle 客户端需要手动配置,跳过自动安装"; \
fi; \
\
if [ -n "$TOOLS" ]; then \
echo "----------------------------------------"; \
echo "[步骤 8/10] 安装系统工具"; \
echo "----------------------------------------"; \
ALPINE_TOOLS=""; \
for tool in $TOOLS; do \
case "$tool" in \
net-tools) ALPINE_TOOLS="$ALPINE_TOOLS net-tools" ;; \
procps) ALPINE_TOOLS="$ALPINE_TOOLS procps" ;; \
iputils-ping) ALPINE_TOOLS="$ALPINE_TOOLS iputils" ;; \
vim) ALPINE_TOOLS="$ALPINE_TOOLS vim" ;; \
htop) ALPINE_TOOLS="$ALPINE_TOOLS htop" ;; \
nano) ALPINE_TOOLS="$ALPINE_TOOLS nano" ;; \
telnet) ALPINE_TOOLS="$ALPINE_TOOLS busybox-extras" ;; \
lsof) ALPINE_TOOLS="$ALPINE_TOOLS lsof" ;; \
ss) ALPINE_TOOLS="$ALPINE_TOOLS iproute2-ss" ;; \
*) ALPINE_TOOLS="$ALPINE_TOOLS $tool" ;; \
esac; \
done; \
if [ -n "$ALPINE_TOOLS" ]; then \
retry_command "apk add --no-cache $ALPINE_TOOLS" || echo "部分工具安装失败"; \
fi; \
fi; \
\
echo "----------------------------------------"; \
echo "[步骤 9/10] 配置 GD 扩展依赖"; \
echo "----------------------------------------"; \
docker-php-ext-configure gd \
--with-freetype \
--with-jpeg \
--with-webp || echo "GD 配置失败,继续..."; \
\
echo "----------------------------------------"; \
echo "[步骤 10/10] 清理缓存"; \
echo "----------------------------------------"; \
rm -rf /var/cache/apk/*; \
\
# Debian 系统配置
else \
echo "----------------------------------------"; \
echo "[步骤 1/10] 配置 Debian 软件源"; \
echo "----------------------------------------"; \
if [ "$CUSTOM_SOURCE" = "true" ]; then \
echo "使用自定义镜像源"; \
cat /tmp/source.list > /etc/apt/sources.list; \
cat /etc/apt/sources.list; \
fi; \
\
echo "----------------------------------------"; \
echo "[步骤 2/10] 更新软件包索引"; \
echo "----------------------------------------"; \
retry_command "apt-get update" || exit 1; \
\
echo "----------------------------------------"; \
echo "[步骤 3/10] 安装基础工具"; \
echo "----------------------------------------"; \
retry_command "apt-get install -y --no-install-recommends curl wget ca-certificates git vim unzip" || exit 1; \
rm -rf /var/lib/apt/lists/*; apt-get clean; \
\
echo "----------------------------------------"; \
echo "[步骤 4/10] 安装开发库"; \
echo "----------------------------------------"; \
apt-get update; \
retry_command "apt-get install -y --no-install-recommends \
libzip-dev zlib1g-dev libpng-dev libjpeg-dev \
libfreetype6-dev libwebp-dev libxpm-dev \
libssl-dev libcurl4-openssl-dev" || exit 1; \
rm -rf /var/lib/apt/lists/*; apt-get clean; \
\
echo "----------------------------------------"; \
echo "[步骤 5/10] 安装 XML 和基础数据库库"; \
echo "----------------------------------------"; \
apt-get update; \
retry_command "apt-get install -y --no-install-recommends \
libxml2-dev libonig-dev libsqlite3-dev" || exit 1; \
rm -rf /var/lib/apt/lists/*; apt-get clean; \
\
echo "----------------------------------------"; \
echo "[步骤 6/10] 安装编译工具"; \
echo "----------------------------------------"; \
apt-get update; \
retry_command "apt-get install -y --no-install-recommends \
autoconf g++ make pkg-config" || exit 1; \
rm -rf /var/lib/apt/lists/*; apt-get clean; \
\
echo "----------------------------------------"; \
echo "[步骤 7/10] 安装数据库扩展依赖"; \
echo "----------------------------------------"; \
if echo "$DATABASE_EXTENSIONS" | grep -q "pgsql"; then \
echo "安装 PostgreSQL 客户端库..."; \
apt-get update; \
retry_command "apt-get install -y --no-install-recommends libpq-dev postgresql-client" || echo "PostgreSQL 依赖安装失败"; \
rm -rf /var/lib/apt/lists/*; apt-get clean; \
fi; \
if echo "$DATABASE_EXTENSIONS" | grep -q "sqlsrv"; then \
echo "安装 SQL Server ODBC 驱动..."; \
apt-get update; \
retry_command "apt-get install -y --no-install-recommends \
apt-transport-https gnupg2 unixodbc-dev" || echo "基础依赖安装失败"; \
curl https://packages.microsoft.com/keys/microsoft.asc | apt-key add - || echo "添加 MS 密钥失败"; \
curl https://packages.microsoft.com/config/debian/11/prod.list > /etc/apt/sources.list.d/mssql-release.list || echo "添加 MS 源失败"; \
apt-get update; \
ACCEPT_EULA=Y retry_command "apt-get install -y --no-install-recommends msodbcsql18" || echo "ODBC 驱动安装失败"; \
ACCEPT_EULA=Y retry_command "apt-get install -y --no-install-recommends mssql-tools18" || echo "SQL Server 工具安装失败"; \
rm -rf /var/lib/apt/lists/*; apt-get clean; \
fi; \
if echo "$DATABASE_EXTENSIONS" | grep -q "oci8"; then \
echo "Oracle Instant Client 需要手动下载和配置"; \
echo "请参考: https://www.oracle.com/database/technologies/instant-client.html"; \
fi; \
\
if [ -n "$TOOLS" ]; then \
echo "----------------------------------------"; \
echo "[步骤 8/10] 安装系统工具"; \
echo "----------------------------------------"; \
apt-get update; \
# 将工具名称映射到正确的 Debian 包名
DEBIAN_TOOLS=""; \
for tool in $TOOLS; do \
case "$tool" in \
ss) DEBIAN_TOOLS="$DEBIAN_TOOLS iproute2" ;; \
lsof) DEBIAN_TOOLS="$DEBIAN_TOOLS lsof" ;; \
*) DEBIAN_TOOLS="$DEBIAN_TOOLS $tool" ;; \
esac; \
done; \
retry_command "apt-get install -y --no-install-recommends $DEBIAN_TOOLS" || echo "部分工具安装失败"; \
rm -rf /var/lib/apt/lists/*; apt-get clean; \
fi; \
\
echo "----------------------------------------"; \
echo "[步骤 9/10] 配置 GD 扩展依赖"; \
echo "----------------------------------------"; \
docker-php-ext-configure gd \
--with-freetype \
--with-jpeg \
--with-webp || echo "GD 配置失败,继续..."; \
\
echo "----------------------------------------"; \
echo "[步骤 10/10] 清理缓存"; \
echo "----------------------------------------"; \
rm -rf /var/lib/apt/lists/*; \
apt-get clean; \
fi; \
\
# 清理临时文件
rm -rf /tmp/source.list; \
\
echo "========================================"; \
echo "基础镜像构建完成"; \
echo "========================================";
# 健康检查
RUN php -v && php -m
# 设置默认工作目录
WORKDIR /var/www/html
-
该文件注释已标注在文档中
-
简要说明
- 执行该文件,会生成类似于 php-base:8.2-debian php-base:8.2-alpine 的基础镜像
- 基础镜像的生成规则
-
镜像名称规则:固定值 php-base
-
tag 标签生成规则:自动读取配置文件中的 基础镜像名,获取版本号 和 对应的系统信息(debian/alpine)作为 tag 名称
-
Dockerfile.final
# =============================================================================
# PHP 最终镜像 Dockerfile(多阶段构建)
# 功能:基于基础镜像安装 PHP 扩展、配置 PHP 和 Composer
# 说明:此文件不需要手动修改,所有配置通过 build.conf 传入
# =============================================================================
# 构建参数:基础镜像名称
ARG BASE_IMAGE_NAME
FROM ${BASE_IMAGE_NAME}
# 扩展和配置参数
ARG SYSTEM_TYPE
ARG PECL_EXTENSIONS=""
ARG LOCAL_EXTENSIONS=""
ARG DATABASE_EXTENSIONS=""
ARG PHP_INI_CONFIGS=""
ARG ENV_VARS=""
ARG COMPOSER_MIRROR="auto"
ARG BUILD_MODE="dev"
ARG WORKDIR="/var/www/html"
# 设置工作目录
WORKDIR /tmp/build
RUN mkdir -p /tmp/pecl /host/pecl
COPY pecl /tmp/pecl-source
RUN set -eux; \
if [ -d "/tmp/pecl-source" ] && [ "$(ls -A /tmp/pecl-source/*.tgz 2>/dev/null)" ]; then \
echo "[缓存] 发现本地 PECL 扩展包"; \
cp /tmp/pecl-source/*.tgz /tmp/pecl/ 2>/dev/null || true; \
ls -lh /tmp/pecl/; \
else \
echo "[信息] 未发现本地 PECL 扩展缓存,将从远程下载"; \
fi; \
rm -rf /tmp/pecl-source;
# 复制本地扩展目录(使用通配符,不存在时自动忽略)
# 已经通过上面的 RUN 命令处理,不再需要
# 安装数据库扩展
RUN set -eux; \
echo "========================================"; \
echo "开始安装数据库扩展"; \
echo "系统类型: ${SYSTEM_TYPE}"; \
echo "数据库扩展: ${DATABASE_EXTENSIONS}"; \
echo "========================================"; \
\
# 定义重试函数
retry_command() { \
local max_attempts=3; \
local attempt=1; \
local cmd="$@"; \
while [ $attempt -le $max_attempts ]; do \
echo "[尝试 ${attempt}/${max_attempts}] 执行: ${cmd}"; \
if eval "$cmd"; then \
echo "[成功] 命令执行成功"; \
return 0; \
fi; \
echo "[失败] 命令执行失败,等待 5 秒后重试..."; \
sleep 5; \
attempt=$((attempt + 1)); \
done; \
echo "[错误] 命令执行失败,已达到最大重试次数"; \
return 1; \
}; \
\
# 安装数据库扩展
if [ -n "$DATABASE_EXTENSIONS" ]; then \
for db_ext in $DATABASE_EXTENSIONS; do \
case "$db_ext" in \
pgsql) \
echo "========================================"; \
echo "[数据库] PostgreSQL 扩展安装"; \
echo "========================================"; \
if [ "$SYSTEM_TYPE" = "alpine" ]; then \
echo "[步骤 1/3] 安装 PostgreSQL 运行时库..."; \
apk add --no-cache postgresql-client libpq; \
rm -rf /var/cache/apk/*; \
else \
echo "[步骤 1/3] 安装 PostgreSQL 运行时库..."; \
apt-get update; \
apt-get install -y --no-install-recommends libpq5 postgresql-client; \
rm -rf /var/lib/apt/lists/*; apt-get clean; \
fi; \
echo "[步骤 2/3] 编译安装 pdo_pgsql 和 pgsql 扩展..."; \
docker-php-ext-install -j1 pdo_pgsql pgsql; \
echo "[步骤 3/3] 验证扩展安装..."; \
php -m | grep -i pgsql || { echo "[错误] PostgreSQL 扩展安装失败"; exit 1; }; \
echo "[成功] PostgreSQL 扩展安装完成"; \
;; \
sqlsrv|pdo_sqlsrv) \
echo "========================================"; \
echo "[数据库] SQL Server 扩展安装"; \
echo "========================================"; \
if [ "$SYSTEM_TYPE" = "alpine" ]; then \
echo "[警告] Alpine 系统暂不支持 SQL Server 扩展,跳过..."; \
else \
echo "[步骤 1/5] 安装 ODBC 依赖..."; \
apt-get update; \
apt-get install -y --no-install-recommends \
apt-transport-https \
gnupg2 \
unixodbc-dev; \
rm -rf /var/lib/apt/lists/*; apt-get clean; \
\
echo "[步骤 2/5] 添加 Microsoft 软件源..."; \
curl -fsSL https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor -o /usr/share/keyrings/microsoft-archive-keyring.gpg; \
echo "deb [arch=amd64 signed-by=/usr/share/keyrings/microsoft-archive-keyring.gpg] https://packages.microsoft.com/debian/11/prod bullseye main" > /etc/apt/sources.list.d/mssql-release.list; \
\
echo "[步骤 3/5] 安装 ODBC 驱动..."; \
apt-get update; \
ACCEPT_EULA=Y apt-get install -y --no-install-recommends msodbcsql18 mssql-tools18; \
echo 'export PATH="$PATH:/opt/mssql-tools18/bin"' >> ~/.bashrc; \
rm -rf /var/lib/apt/lists/*; apt-get clean; \
\
echo "[步骤 4/5] 安装 SQL Server PHP 扩展..."; \
pecl install sqlsrv pdo_sqlsrv; \
docker-php-ext-enable sqlsrv pdo_sqlsrv; \
\
echo "[步骤 5/5] 验证扩展安装..."; \
php -m | grep -i sqlsrv || { echo "[错误] SQL Server 扩展安装失败"; exit 1; }; \
echo "[成功] SQL Server 扩展安装完成"; \
\
echo "[配置] 设置 ODBC 驱动路径..."; \
echo "" >> /usr/local/etc/php/conf.d/sqlsrv.ini; \
echo "; SQL Server 扩展配置" >> /usr/local/etc/php/conf.d/sqlsrv.ini; \
echo "sqlsrv.WarningsReturnAsErrors = 0" >> /usr/local/etc/php/conf.d/sqlsrv.ini; \
fi; \
;; \
oci8|pdo_oci) \
echo "========================================"; \
echo "[数据库] Oracle 扩展安装"; \
echo "========================================"; \
echo "[提示] Oracle Instant Client 需要手动下载和配置"; \
echo "[步骤 1] 下载 Oracle Instant Client:"; \
echo " https://www.oracle.com/database/technologies/instant-client/downloads.html"; \
echo "[步骤 2] 将文件放置在 /opt/oracle 目录"; \
echo "[步骤 3] 设置环境变量:"; \
echo " export LD_LIBRARY_PATH=/opt/oracle/instantclient_21_1:\$LD_LIBRARY_PATH"; \
echo "[步骤 4] 安装扩展:"; \
echo " pecl install oci8"; \
echo " docker-php-ext-enable oci8"; \
echo "[跳过] 需要手动配置 Oracle 客户端"; \
;; \
esac; \
done; \
echo "[成功] 所有数据库扩展处理完成"; \
else \
echo "[信息] 未配置数据库扩展"; \
fi; \
\
# 清理临时文件
rm -rf /tmp/pear; \
if [ "$SYSTEM_TYPE" = "alpine" ]; then \
rm -rf /var/cache/apk/*; \
else \
rm -rf /var/lib/apt/lists/*; \
apt-get clean; \
fi;
# 安装 PHP 扩展
RUN set -eux; \
echo "========================================"; \
echo "开始安装 PHP 扩展"; \
echo "系统类型: ${SYSTEM_TYPE}"; \
echo "LOCAL 扩展: ${LOCAL_EXTENSIONS}"; \
echo "PECL 扩展: ${PECL_EXTENSIONS}"; \
echo "========================================"; \
\
cd /tmp || exit 1; \
\
# 定义重试函数
retry_command() { \
local max_attempts=3; \
local attempt=1; \
local cmd="$@"; \
while [ $attempt -le $max_attempts ]; do \
echo "[尝试 ${attempt}/${max_attempts}] 执行: ${cmd}"; \
if eval "$cmd"; then \
echo "[成功] 命令执行成功"; \
return 0; \
fi; \
echo "[失败] 命令执行失败,等待 5 秒后重试..."; \
sleep 5; \
attempt=$((attempt + 1)); \
done; \
echo "[错误] 命令执行失败,已达到最大重试次数"; \
return 1; \
}; \
\
if [ -n "$LOCAL_EXTENSIONS" ]; then \
echo "----------------------------------------"; \
echo "安装 LOCAL 扩展: $LOCAL_EXTENSIONS"; \
echo "----------------------------------------"; \
\
for ext in $(echo "$LOCAL_EXTENSIONS" | tr ',' ' '); do \
[ -z "$ext" ] && continue; \
case "$ext" in \
gd) echo "[扩展] GD - 图像处理库" ;; \
zip) \
echo "[扩展] ZIP - 压缩文件支持"; \
if [ "$SYSTEM_TYPE" = "alpine" ]; then \
apk add --no-cache libzip-dev; \
rm -rf /var/cache/apk/*; \
fi; \
;; \
pdo_mysql) echo "[扩展] PDO_MYSQL - MySQL PDO 驱动" ;; \
mysqli) echo "[扩展] MYSQLI - MySQL 改进扩展" ;; \
opcache) echo "[扩展] OPCACHE - 操作码缓存" ;; \
pcntl) echo "[扩展] PCNTL - 进程控制" ;; \
bcmath) echo "[扩展] BCMATH - 高精度数学运算" ;; \
sockets) echo "[扩展] SOCKETS - Socket 通信" ;; \
*) echo "[扩展] $ext" ;; \
esac; \
done; \
\
echo "开始批量安装 LOCAL 扩展(单核编译)..."; \
if ! retry_command "docker-php-ext-install -j1 $LOCAL_EXTENSIONS"; then \
echo "[错误] LOCAL 扩展安装失败"; \
exit 1; \
fi; \
echo "[成功] LOCAL 扩展安装成功"; \
\
rm -rf /tmp/pear /tmp/*.tgz; \
if [ "$SYSTEM_TYPE" = "alpine" ]; then \
rm -rf /var/cache/apk/*; \
else \
rm -rf /var/lib/apt/lists/*; \
apt-get clean; \
fi; \
fi; \
\
mkdir -p /tmp/pecl-work; \
\
if [ -n "$PECL_EXTENSIONS" ]; then \
echo "----------------------------------------"; \
echo "安装 PECL 扩展: $PECL_EXTENSIONS"; \
echo "----------------------------------------"; \
\
for ext in $(echo "$PECL_EXTENSIONS" | tr ',' ' '); do \
[ -z "$ext" ] && continue; \
ext_name=$(echo "$ext" | cut -d'-' -f1); \
ext_version=$(echo "$ext" | grep -oP '\-\K.*' || echo ""); \
\
echo "========================================"; \
echo "[处理] PECL 扩展: $ext_name"; \
if [ -n "$ext_version" ]; then \
echo "[版本] $ext_version"; \
else \
echo "[版本] 最新稳定版"; \
fi; \
echo "========================================"; \
\
cd /tmp/pecl-work || exit 1; \
rm -rf /tmp/pecl-work/*; \
\
local_ext_file="/tmp/pecl/${ext}.tgz"; \
host_ext_file="/host/pecl/${ext}.tgz"; \
ext_exists=false; \
\
if [ -f "$local_ext_file" ]; then \
echo "[缓存] 发现本地扩展文件: ${ext}.tgz"; \
ext_exists=true; \
fi; \
\
# 根据扩展类型配置编译选项
case "$ext_name" in \
swoole) \
echo "[配置] Swoole 扩展 - 启用完整功能支持"; \
configure_opts='enable-openssl="yes" enable-sockets="yes" enable-http2="yes" enable-mysqlnd="yes" enable-swoole-curl="yes"'; \
\
if [ "$ext_exists" = true ]; then \
echo "[安装] 使用本地缓存安装"; \
if ! retry_command "pecl install --configureoptions '$configure_opts' $local_ext_file"; then \
echo "[警告] 本地安装失败,尝试 PECL 仓库"; \
ext_exists=false; \
fi; \
fi; \
\
if [ "$ext_exists" = false ]; then \
echo "[下载] 从 PECL 仓库下载"; \
if ! retry_command "pecl download $ext"; then \
echo "[错误] 扩展下载失败"; \
exit 1; \
fi; \
downloaded_file=$(ls /tmp/pecl-work/${ext_name}-*.tgz 2>/dev/null | head -n 1); \
if [ -f "$downloaded_file" ]; then \
echo "[保存] 保存扩展到 pecl 目录: ${ext}.tgz"; \
cp "$downloaded_file" "$host_ext_file" 2>/dev/null || echo "[警告] 无法保存到宿主机"; \
cp "$downloaded_file" "$local_ext_file"; \
echo "[安装] 安装扩展"; \
if ! retry_command "pecl install --configureoptions '$configure_opts' $downloaded_file"; then \
echo "[错误] 扩展安装失败"; \
exit 1; \
fi; \
fi; \
fi; \
;; \
redis) \
echo "[配置] Redis 扩展 - 启用 LZF 压缩"; \
configure_opts='enable-redis-igbinary="no" enable-redis-msgpack="no" enable-redis-lzf="yes" enable-redis-zstd="no"'; \
\
if [ "$ext_exists" = true ]; then \
echo "[安装] 使用本地缓存安装"; \
if ! retry_command "pecl install --configureoptions '$configure_opts' $local_ext_file"; then \
echo "[警告] 本地安装失败,尝试 PECL 仓库"; \
ext_exists=false; \
fi; \
fi; \
\
if [ "$ext_exists" = false ]; then \
echo "[下载] 从 PECL 仓库下载"; \
if ! retry_command "pecl download $ext"; then \
echo "[错误] 扩展下载失败"; \
exit 1; \
fi; \
downloaded_file=$(ls /tmp/pecl-work/${ext_name}-*.tgz 2>/dev/null | head -n 1); \
if [ -f "$downloaded_file" ]; then \
echo "[保存] 保存扩展到 pecl 目录: ${ext}.tgz"; \
cp "$downloaded_file" "$host_ext_file" 2>/dev/null || echo "[警告] 无法保存到宿主机"; \
cp "$downloaded_file" "$local_ext_file"; \
echo "[安装] 安装扩展"; \
if ! retry_command "pecl install --configureoptions '$configure_opts' $downloaded_file"; then \
echo "[错误] 扩展安装失败"; \
exit 1; \
fi; \
fi; \
fi; \
;; \
mongodb) \
echo "[配置] MongoDB 扩展 - 使用系统 OpenSSL"; \
configure_opts='enable-mongodb-ssl="yes"'; \
\
if [ "$ext_exists" = true ]; then \
echo "[安装] 使用本地缓存安装"; \
if ! retry_command "pecl install --configureoptions '$configure_opts' $local_ext_file"; then \
echo "[警告] 本地安装失败,尝试 PECL 仓库"; \
ext_exists=false; \
fi; \
fi; \
\
if [ "$ext_exists" = false ]; then \
echo "[下载] 从 PECL 仓库下载"; \
if ! retry_command "pecl download $ext"; then \
echo "[错误] 扩展下载失败"; \
exit 1; \
fi; \
downloaded_file=$(ls /tmp/pecl-work/${ext_name}-*.tgz 2>/dev/null | head -n 1); \
if [ -f "$downloaded_file" ]; then \
echo "[保存] 保存扩展到 pecl 目录: ${ext}.tgz"; \
cp "$downloaded_file" "$host_ext_file" 2>/dev/null || echo "[警告] 无法保存到宿主机"; \
cp "$downloaded_file" "$local_ext_file"; \
echo "[安装] 安装扩展"; \
if ! retry_command "pecl install --configureoptions '$configure_opts' $downloaded_file"; then \
echo "[错误] 扩展安装失败"; \
exit 1; \
fi; \
fi; \
fi; \
;; \
*) \
echo "[配置] $ext_name 扩展 - 使用默认配置"; \
\
if [ "$ext_exists" = true ]; then \
echo "[安装] 使用本地缓存安装"; \
if ! retry_command "pecl install $local_ext_file"; then \
echo "[警告] 本地安装失败,尝试 PECL 仓库"; \
ext_exists=false; \
fi; \
fi; \
\
if [ "$ext_exists" = false ]; then \
echo "[下载] 从 PECL 仓库下载"; \
if ! retry_command "pecl download $ext"; then \
echo "[错误] 扩展下载失败"; \
exit 1; \
fi; \
downloaded_file=$(ls /tmp/pecl-work/${ext_name}-*.tgz 2>/dev/null | head -n 1); \
if [ -f "$downloaded_file" ]; then \
echo "[保存] 保存扩展到 pecl 目录: ${ext}.tgz"; \
cp "$downloaded_file" "$host_ext_file" 2>/dev/null || echo "[警告] 无法保存到宿主机"; \
cp "$downloaded_file" "$local_ext_file"; \
echo "[安装] 安装扩展"; \
if ! retry_command "pecl install $downloaded_file"; then \
echo "[错误] 扩展安装失败"; \
exit 1; \
fi; \
fi; \
fi; \
;; \
esac; \
\
echo "[启用] 启用 $ext_name 扩展"; \
docker-php-ext-enable "$ext_name" || echo "[警告] 无法启用 $ext_name,可能已经启用"; \
\
echo "[验证] 验证扩展安装"; \
if php -m | grep -i "$ext_name"; then \
echo "[成功] $ext_name 扩展安装并启用成功"; \
else \
echo "[警告] $ext_name 扩展可能未正确加载"; \
fi; \
\
echo "========================================"; \
done; \
\
echo "[成功] 所有 PECL 扩展安装完成"; \
\
cd /tmp || exit 1; \
rm -rf /tmp/pecl-work; \
rm -rf /tmp/pear /tmp/*.tgz; \
if [ "$SYSTEM_TYPE" = "alpine" ]; then \
rm -rf /var/cache/apk/*; \
else \
rm -rf /var/lib/apt/lists/*; \
apt-get clean; \
fi; \
fi; \
\
# 最终验证
echo "========================================"; \
echo "PHP 扩展安装完成"; \
echo "已安装的扩展列表:"; \
php -m; \
echo "========================================";
# 配置 PHP.ini
RUN set -eux; \
if [ -n "$PHP_INI_CONFIGS" ]; then \
echo "========================================"; \
echo "配置 PHP.ini"; \
echo "========================================"; \
\
echo "$PHP_INI_CONFIGS" | tr '|' '\n' | while read -r config; do \
[ -z "$config" ] && continue; \
echo "[配置] $config"; \
echo "$config" >> /usr/local/etc/php/conf.d/custom.ini; \
done; \
\
echo "----------------------------------------"; \
echo "PHP.ini 配置内容:"; \
cat /usr/local/etc/php/conf.d/custom.ini; \
echo "========================================"; \
fi;
# 设置环境变量
RUN set -eux; \
if [ -n "$ENV_VARS" ]; then \
echo "========================================"; \
echo "配置环境变量"; \
echo "========================================"; \
\
echo "$ENV_VARS" | tr '|' '\n' | while read -r env_var; do \
[ -z "$env_var" ] && continue; \
echo "[环境变量] $env_var"; \
echo "export $env_var" >> /etc/profile; \
done; \
\
echo "[成功] 环境变量配置完成"; \
fi;
# 安装 Composer
RUN set -eux; \
echo "========================================"; \
echo "安装 Composer"; \
echo "Composer 镜像: ${COMPOSER_MIRROR}"; \
echo "========================================"; \
\
retry_command() { \
local max_attempts=3; \
local attempt=1; \
local cmd="$@"; \
while [ $attempt -le $max_attempts ]; do \
echo "[尝试 ${attempt}/${max_attempts}]"; \
if eval "$cmd"; then \
return 0; \
fi; \
sleep 5; \
attempt=$((attempt + 1)); \
done; \
return 1; \
}; \
\
retry_command "curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer"; \
\
case "$COMPOSER_MIRROR" in \
aliyun) \
echo "[配置] 阿里云 Composer 镜像"; \
composer config -g repo.packagist composer https://mirrors.aliyun.com/composer/; \
;; \
tencent) \
echo "[配置] 腾讯云 Composer 镜像"; \
composer config -g repo.packagist composer https://mirrors.cloud.tencent.com/composer/; \
;; \
ustc) \
echo "[配置] 中科大 Composer 镜像"; \
composer config -g repo.packagist composer https://packagist.mirrors.ustc.edu.cn/; \
;; \
packagist) \
echo "[配置] 官方 Packagist 源"; \
;; \
auto) \
echo "[配置] 自动检测,使用阿里云镜像"; \
composer config -g repo.packagist composer https://mirrors.aliyun.com/composer/; \
;; \
*) \
echo "[警告] 未知的 Composer 镜像配置,使用默认源"; \
;; \
esac; \
\
composer --version; \
echo "[成功] Composer 安装完成";
# 清理和优化
RUN set -eux; \
echo "========================================"; \
echo "清理临时文件和优化镜像"; \
echo "构建模式: ${BUILD_MODE}"; \
echo "========================================"; \
\
rm -rf /tmp/pear; \
rm -rf /tmp/pecl; \
\
if [ "$BUILD_MODE" = "prod" ]; then \
echo "[模式] 生产模式 - 清理编译工具以优化镜像大小"; \
if [ "$SYSTEM_TYPE" = "alpine" ]; then \
apk del autoconf g++ make linux-headers; \
rm -rf /var/cache/apk/*; \
else \
apt-get purge -y autoconf g++ make pkg-config; \
apt-get autoremove -y; \
rm -rf /var/lib/apt/lists/*; \
apt-get clean; \
fi; \
else \
echo "[模式] 开发模式 - 保留编译工具"; \
if [ "$SYSTEM_TYPE" = "alpine" ]; then \
rm -rf /var/cache/apk/*; \
else \
rm -rf /var/lib/apt/lists/*; \
apt-get clean; \
fi; \
fi; \
\
rm -rf /tmp/*; \
rm -rf /var/tmp/*; \
\
echo "[成功] 清理完成";
# 最终验证
RUN set -eux; \
echo "========================================"; \
echo "最终验证"; \
echo "========================================"; \
echo "PHP 版本:"; \
php -v; \
echo ""; \
echo "已安装的扩展:"; \
php -m; \
echo ""; \
echo "Composer 版本:"; \
composer --version; \
echo "========================================"; \
echo "✅ 镜像构建完成!"; \
echo "========================================";
# 设置工作目录
ARG WORKDIR
WORKDIR ${WORKDIR}
- 该文件注释已标注在文档中
- 简要说明
- 执行该文件,会生成你指定的镜像名称和 tag 标签
- 使用多阶段构建模式,第一阶段: 安装 pecl 和 local 扩展;第二阶段: 构建最终镜像
- 执行该文件,会生成你指定的镜像名称和 tag 标签
build.sh
#!/bin/bash
# =============================================================================
# PHP Docker 多阶段构建脚本
# 功能:根据配置文件自动构建 PHP 基础镜像和最终镜像
# 用法:./build.sh [最终镜像名称]
# 版本:2.0
# =============================================================================
set -e # 遇到错误立即退出
# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
PURPLE='\033[0;35m'
NC='\033[0m' # 无颜色
# 配置文件路径
CONFIG_FILE="./config/build.conf"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
BUILD_LOG_FILE="./logs/build_$(date '+%Y%m%d_%H%M%S').log"
# 日志函数
log_info() {
local msg="[INFO] $(date '+%Y-%m-%d %H:%M:%S') - $1"
echo -e "${BLUE}${msg}${NC}"
echo "$msg" >> "$BUILD_LOG_FILE"
}
log_success() {
local msg="[SUCCESS] $(date '+%Y-%m-%d %H:%M:%S') - $1"
echo -e "${GREEN}${msg}${NC}"
echo "$msg" >> "$BUILD_LOG_FILE"
}
log_warn() {
local msg="[WARN] $(date '+%Y-%m-%d %H:%M:%S') - $1"
echo -e "${YELLOW}${msg}${NC}"
echo "$msg" >> "$BUILD_LOG_FILE"
}
log_error() {
local msg="[ERROR] $(date '+%Y-%m-%d %H:%M:%S') - $1"
echo -e "${RED}${msg}${NC}"
echo "$msg" >> "$BUILD_LOG_FILE"
}
log_step() {
local msg="[STEP] $(date '+%Y-%m-%d %H:%M:%S') - $1"
echo -e "${CYAN}${msg}${NC}"
echo "$msg" >> "$BUILD_LOG_FILE"
}
# 显示横幅
show_banner() {
echo -e "${PURPLE}"
echo "========================================================"
echo " PHP Docker 多阶段构建系统 v2.0"
echo " Build Time: $(date '+%Y-%m-%d %H:%M:%S')"
echo "========================================================"
echo -e "${NC}"
}
# 检查配置文件是否存在
check_config_file() {
log_step "检查配置文件"
if [ ! -f "$CONFIG_FILE" ]; then
log_error "配置文件不存在: $CONFIG_FILE"
exit 1
fi
log_success "配置文件检查通过: $CONFIG_FILE"
}
# 解析配置文件
parse_config() {
local section=""
log_step "开始解析配置文件"
while IFS= read -r line || [ -n "$line" ]; do
# 跳过空行和注释
[[ -z "$line" || "$line" =~ ^[[:space:]]*# ]] && continue
# 检测 section
if [[ "$line" =~ ^\[(.+)\]$ ]]; then
section="${BASH_REMATCH[1]}"
continue
fi
# 读取配置值
if [ -n "$section" ]; then
case "$section" in
base_image)
BASE_IMAGE="$line"
;;
pecl)
PECL_EXTENSIONS+=("$line")
;;
local)
LOCAL_EXTENSIONS+=("$line")
;;
database)
DATABASE_EXTENSIONS+=("$line")
;;
tools)
TOOLS+=("$line")
;;
mode)
BUILD_MODE="$line"
;;
custom_source)
CUSTOM_SOURCE="$line"
;;
composer_mirror)
COMPOSER_MIRROR="$line"
;;
php)
PHP_INI_CONFIGS+=("$line")
;;
build_target)
BUILD_TARGET="$line"
;;
force_rebuild_base)
FORCE_REBUILD_BASE="$line"
;;
workdir)
WORKDIR="$line"
;;
healthcheck)
HEALTHCHECK="$line"
;;
env)
ENV_VARS+=("$line")
;;
esac
fi
done < "$CONFIG_FILE"
log_success "配置文件解析完成"
}
# 检测系统类型(Alpine 或 Debian)
detect_system_type() {
log_step "检测系统类型"
if [[ "$BASE_IMAGE" == *"alpine"* ]]; then
SYSTEM_TYPE="alpine"
else
SYSTEM_TYPE="debian"
fi
log_info "系统类型: $SYSTEM_TYPE"
}
# 生成基础镜像名称
generate_base_image_name() {
log_step "生成基础镜像名称"
local version=$(echo "$BASE_IMAGE" | grep -oP 'php:\K[0-9]+\.[0-9]+')
BASE_IMAGE_NAME="php-base:${version}-${SYSTEM_TYPE}"
log_info "基础镜像名称: $BASE_IMAGE_NAME"
}
# 检查基础镜像是否存在
check_base_image_exists() {
log_step "检查基础镜像是否存在"
if docker image inspect "$BASE_IMAGE_NAME" >/dev/null 2>&1; then
log_success "基础镜像已存在: $BASE_IMAGE_NAME"
return 0
else
log_warn "基础镜像不存在,需要构建"
return 1
fi
}
# 显示配置摘要
show_config_summary() {
echo -e "${CYAN}"
echo "========================================================"
echo " 构建配置摘要"
echo "========================================================"
echo -e "${NC}"
echo " 基础镜像: $BASE_IMAGE"
echo " 系统类型: $SYSTEM_TYPE"
echo " 基础镜像名称: $BASE_IMAGE_NAME"
echo " PECL 扩展数量: ${#PECL_EXTENSIONS[@]}"
echo " LOCAL 扩展数量: ${#LOCAL_EXTENSIONS[@]}"
echo " 数据库扩展数量: ${#DATABASE_EXTENSIONS[@]}"
echo " 系统工具数量: ${#TOOLS[@]}"
echo " 构建模式: $BUILD_MODE"
echo " 自定义源: $CUSTOM_SOURCE"
echo " Composer 镜像: $COMPOSER_MIRROR"
echo " 工作目录: $WORKDIR"
echo " 构建目标: $BUILD_TARGET"
echo " 强制重建基础: $FORCE_REBUILD_BASE"
echo " 日志文件: $BUILD_LOG_FILE"
echo -e "${CYAN}"
echo "========================================================"
echo -e "${NC}"
}
# 构建基础镜像
build_base_image() {
log_step "开始构建基础镜像"
# 准备工具参数
local tools_arg=""
if [ ${#TOOLS[@]} -gt 0 ]; then
tools_arg=$(IFS=' '; echo "${TOOLS[*]}")
fi
local database_extensions_arg=""
if [ ${#DATABASE_EXTENSIONS[@]} -gt 0 ]; then
database_extensions_arg=$(IFS=' '; echo "${DATABASE_EXTENSIONS[*]}")
fi
echo -e "${YELLOW}正在构建基础镜像,请稍候...${NC}"
# 构建基础镜像
docker build \
--file Dockerfile.base \
--build-arg BASE_IMAGE="$BASE_IMAGE" \
--build-arg SYSTEM_TYPE="$SYSTEM_TYPE" \
--build-arg CUSTOM_SOURCE="$CUSTOM_SOURCE" \
--build-arg TOOLS="$tools_arg" \
--build-arg DATABASE_EXTENSIONS="$database_extensions_arg" \
--tag "$BASE_IMAGE_NAME" \
--progress=plain \
. 2>&1 | tee -a "$BUILD_LOG_FILE"
if [ ${PIPESTATUS[0]} -eq 0 ]; then
log_success "基础镜像构建成功: $BASE_IMAGE_NAME"
else
log_error "基础镜像构建失败,请查看日志: $BUILD_LOG_FILE"
exit 1
fi
}
# 构建最终镜像
build_final_image() {
local final_image_name="$1"
if [ -z "$final_image_name" ]; then
log_error "请提供最终镜像名称"
echo "用法: ./build.sh [最终镜像名称]"
exit 1
fi
log_step "开始构建最终镜像: $final_image_name"
# 准备 PECL 扩展参数
local pecl_extensions_arg=""
if [ ${#PECL_EXTENSIONS[@]} -gt 0 ]; then
pecl_extensions_arg=$(IFS=,; echo "${PECL_EXTENSIONS[*]}")
fi
# 准备 LOCAL 扩展参数
local local_extensions_arg=""
if [ ${#LOCAL_EXTENSIONS[@]} -gt 0 ]; then
local_extensions_arg=$(IFS=' '; echo "${LOCAL_EXTENSIONS[*]}")
fi
local database_extensions_arg=""
if [ ${#DATABASE_EXTENSIONS[@]} -gt 0 ]; then
database_extensions_arg=$(IFS=' '; echo "${DATABASE_EXTENSIONS[*]}")
fi
# 准备 PHP 配置参数
local php_ini_arg=""
if [ ${#PHP_INI_CONFIGS[@]} -gt 0 ]; then
php_ini_arg=$(IFS='|'; echo "${PHP_INI_CONFIGS[*]}")
fi
# 准备环境变量参数
local env_vars_arg=""
if [ ${#ENV_VARS[@]} -gt 0 ]; then
env_vars_arg=$(IFS='|'; echo "${ENV_VARS[*]}")
fi
mkdir -p ./pecl
log_info "pecl 目录已准备: ./pecl"
echo -e "${YELLOW}正在构建最终镜像,请稍候...${NC}"
# 构建最终镜像
docker build \
--file Dockerfile.final \
--build-arg BASE_IMAGE_NAME="$BASE_IMAGE_NAME" \
--build-arg SYSTEM_TYPE="$SYSTEM_TYPE" \
--build-arg PECL_EXTENSIONS="$pecl_extensions_arg" \
--build-arg LOCAL_EXTENSIONS="$local_extensions_arg" \
--build-arg DATABASE_EXTENSIONS="$database_extensions_arg" \
--build-arg PHP_INI_CONFIGS="$php_ini_arg" \
--build-arg ENV_VARS="$env_vars_arg" \
--build-arg COMPOSER_MIRROR="$COMPOSER_MIRROR" \
--build-arg BUILD_MODE="$BUILD_MODE" \
--build-arg WORKDIR="$WORKDIR" \
--tag "$final_image_name" \
--progress=plain \
. 2>&1 | tee -a "$BUILD_LOG_FILE"
if [ ${PIPESTATUS[0]} -eq 0 ]; then
log_success "最终镜像构建成功: $final_image_name"
echo ""
log_info "镜像详细信息:"
docker images | grep -E "REPOSITORY|$final_image_name"
else
log_error "最终镜像构建失败,请查看日志: $BUILD_LOG_FILE"
exit 1
fi
}
# 生成构建报告
generate_build_report() {
local final_image_name="$1"
local report_file="./logs/build_report_$(date '+%Y%m%d_%H%M%S').txt"
log_step "生成构建报告"
{
echo "========================================================"
echo " PHP Docker 构建报告"
echo "========================================================"
echo ""
echo "构建时间: $(date '+%Y-%m-%d %H:%M:%S')"
echo "最终镜像: $final_image_name"
echo "基础镜像: $BASE_IMAGE_NAME"
echo "系统类型: $SYSTEM_TYPE"
echo "构建模式: $BUILD_MODE"
echo ""
echo "========================================================"
echo " 已安装扩展列表"
echo "========================================================"
docker run --rm "$final_image_name" php -m
echo ""
echo "========================================================"
echo " PHP 版本信息"
echo "========================================================"
docker run --rm "$final_image_name" php -v
echo ""
echo "========================================================"
echo " Composer 版本"
echo "========================================================"
docker run --rm "$final_image_name" composer --version
echo ""
echo "========================================================"
echo " 镜像大小"
echo "========================================================"
docker images | grep "$final_image_name"
echo ""
} > "$report_file"
log_success "构建报告已生成: $report_file"
cat "$report_file"
}
# 清理旧镜像
cleanup_old_images() {
log_step "清理未使用的 Docker 镜像"
echo -e "${YELLOW}是否清理未使用的镜像?(y/N)${NC}"
read -r response
if [[ "$response" =~ ^[Yy]$ ]]; then
docker image prune -f
log_success "镜像清理完成"
else
log_info "跳过镜像清理"
fi
}
# 生成 docker-compose.yml
generate_docker_compose() {
local image_name="$1"
local compose_file="./docker-compose.yml"
log_step "生成 docker-compose.yml"
cat > "$compose_file" <<EOF
version: '3.8'
services:
php:
image: ${image_name}
container_name: php-app
restart: unless-stopped
working_dir: ${WORKDIR}
volumes:
- ./app:${WORKDIR}
networks:
- app-network
environment:
- TZ=Asia/Shanghai
healthcheck:
test: ["CMD", "php", "-v"]
interval: 30s
timeout: 10s
retries: 3
networks:
app-network:
driver: bridge
EOF
log_success "docker-compose.yml 已生成: $compose_file"
}
# 主函数
main() {
show_banner
# 初始化变量
BASE_IMAGE=""
PECL_EXTENSIONS=()
LOCAL_EXTENSIONS=()
DATABASE_EXTENSIONS=()
TOOLS=()
BUILD_MODE="dev"
CUSTOM_SOURCE="false"
COMPOSER_MIRROR="auto"
PHP_INI_CONFIGS=()
BUILD_TARGET="all"
FORCE_REBUILD_BASE="false"
WORKDIR="/var/www/html"
HEALTHCHECK=""
ENV_VARS=()
SYSTEM_TYPE=""
BASE_IMAGE_NAME=""
# 检查配置文件
check_config_file
# 解析配置
parse_config
# 检测系统类型
detect_system_type
# 生成基础镜像名称
generate_base_image_name
# 显示配置摘要
show_config_summary
# 根据构建目标执行
case "$BUILD_TARGET" in
base)
log_info "构建目标: 仅构建基础镜像"
build_base_image
;;
final)
log_info "构建目标: 仅构建最终镜像"
if ! check_base_image_exists; then
log_error "基础镜像不存在,请先构建基础镜像或设置 build_target=all"
exit 1
fi
build_final_image "$1"
generate_build_report "$1"
generate_docker_compose "$1"
;;
all)
log_info "构建目标: 构建基础镜像和最终镜像"
# 检查是否需要构建基础镜像
if [ "$FORCE_REBUILD_BASE" = "true" ]; then
log_warn "强制重新构建基础镜像"
build_base_image
else
if check_base_image_exists; then
log_info "基础镜像已存在,跳过构建"
else
build_base_image
fi
fi
build_final_image "$1"
generate_build_report "$1"
generate_docker_compose "$1"
;;
*)
log_error "无效的构建目标: $BUILD_TARGET"
exit 1
;;
esac
# 询问是否清理旧镜像
cleanup_old_images
echo -e "${GREEN}"
echo "========================================================"
echo " ✅ 构建流程全部完成!"
echo "========================================================"
echo -e "${NC}"
echo "📋 构建日志: $BUILD_LOG_FILE"
echo "🐳 Docker 镜像: $1"
echo "📝 使用方式: docker run -it --rm $1 php -v"
echo "🚀 Docker Compose: docker-compose up -d"
echo ""
}
# 执行主函数
main "$@"
- 该文件是解析配置文件及控制构建情况的文件
执行构建
# 进入项目目录
cd project
# 添加文件执行权限
chmod +x ./build.sh
# 执行构建
./build.sh 新的镜像名称
结语
该份套餐可以做到:
- 构建逻辑通用:不同版本、不同扩展的构建只需要修改 build.conf 配置文件
- 扩展自由度高:可自由增减扩展信息,可指定版本,也可不指定,不指定时安装最新版本
- 构建模式切换:分为 prod/dev,prod 构建会清楚构建缓存等信息,减少镜像体积;dev模式不会,方便多次构建,减少构建时间
- 自定义系统依赖源:自由配置是否使用自定义系统依赖源,减少构建成功率和构建速度
- 自定义php配置:不同的开发需求,可能需要不通过的php配置,可以自由配置,构建后覆盖原有的默认配置
- 构建目标自由:可自由配置构建 基础镜像(base)、最终镜像(final)、基础+最终(all)
- 系统依赖自动检测:可根据配置的基础镜像,自动识别镜像系统(debian/alpine)、并安装对应的依赖,如:系统依赖、系统工具等
- GD 扩展自动配置:如果 local 扩展中存在 GD 扩展,则自动做扩展特殊配置
- 镜像名称自定义:基础镜像名称自动生成,最终镜像名称可以自由定义
- 扩展安装重试机制:安装扩展容易因为各种原因失败,追加重试机制(3次)减少失败次数
okk,提升到此结束,又可以愉快的做牛马了 ... ... ...