按产品分类浏览文章 关于本站

构建基于 Amazon Linux 2023/Ubuntu 24.04 系统 构建 Apache2 + PHP8 运行环境容器(含 Dockerfile)

解决容器内同时启动php-fpm和httpd的单进程限制,给出Ubuntu 24.04(Apache 2.4.58 + PHP 8.3)与Amazon Linux 2023(Apache 2.4.68 + PHP 8.4)经实测验证的Dockerfile与启动脚本,并说明proxy_fcgi对接、/run目录tmpfs覆盖、镜像分层瘦身等工程要点

2026-09 更新了 Ubuntu 24.04 系统镜像

一、背景

1、启动脚本问题

构建容器用于AWS ECS服务和AWS EKS服务的时候,由于容器服务的启动endpoint只能是唯一的启动进程,而且必须处于前台,因此会面临无法同时启动php-fpm和httpd的问题。为了解决这个问题,可增加一个启动服务脚本,然后让容器调用这个单一脚本。

启动脚本src目录下的run_apache.sh内容如下:

mkdir -p /var/run/apache2
mkdir -p /var/lock/apache2
/usr/sbin/php-fpm -D
/usr/sbin/httpd -D FOREGROUND

2、镜像安装包过多体积过大问题

此外,在构建容器中,还经常遇到体积过大,影响服务发布速度等问题。可通过优化分成解决。以上配置中,将安装软件的所有命令写在同一行,可实现一个文件层,由此可显著减少容器的体积。此外,在安装软件时候,执行 yum update -y 可将补丁升级到最新,减少安全风险。

如下配置文件可参考构建容器镜像用于AWS ECS服务和AWS EKS服务。中国区和海外区测试通过。


二、基于 Ubuntu 24.04 构建镜像

Ubuntu 24.04 LTS(代号 Noble Numbat)的官方软件源提供 PHP 8.3 与 Apache 2.4.58,两者都可以直接通过 apt 安装,不需要引入 ondrej/php 之类的第三方 PPA 源。与 Amazon Linux 系统相比,Debian 系发行版在构建 Apache + PHP-FPM 容器时有三处差异需要注意。

第一处差异是 Apache 主程序的名称与启动方式。Debian 系将主程序命名为 apache2 而非 httpd,并且把运行时所需的 APACHE_RUN_USER、APACHE_RUN_GROUP、APACHE_PID_FILE、APACHE_RUN_DIR 等变量集中定义在 /etc/apache2/envvars 文件中。如果在启动脚本里直接执行 /usr/sbin/apache2 而没有先加载该文件,Apache 会因为无法解析配置中的变量而直接退出,因此启动脚本中必须先 source 这个文件,或者改用会自动加载它的 apachectl 命令。

第二处差异是 PHP-FPM 可执行文件带有版本号后缀,路径为 /usr/sbin/php-fpm8.3,而 Amazon Linux 上是不带版本号的 /usr/sbin/php-fpm。同时 Debian 系的 FPM 监听地址默认是 Unix Socket,即 /run/php/php8.3-fpm.sock,而该目录在容器首次启动时并不存在,需要在启动脚本中先创建。

第三处差异体现在 Apache 与 PHP-FPM 的对接方式上。apt 安装的 php8.3-fpm 软件包会自带 /etc/apache2/conf-available/php8.3-fpm.conf 配置片段,其中已经通过 SetHandler 指令把以 .php 结尾的请求转发到上述 Unix Socket,因此只需要启用 proxy_fcgi 与 setenvif 两个模块,再执行 a2enconf php8.3-fpm 让该片段生效即可,无须手工编写 VirtualHost 或 FilesMatch 配置。

这里还有一个容易被忽略的依赖关系陷阱。php8.3 元包的依赖项写法是 libapache2-mod-php8.3 | php8.3-fpm | php8.3-cgi,在使用 –no-install-recommends 安装时 apt 会选择第一个候选项,也就是把已经不推荐在容器中使用的 mod_php 装进镜像。为了确保运行环境走 PHP-FPM 路线,应当显式声明 php8.3-cli 与 php8.3-fpm,而不是安装 php8.3 元包。

启动脚本 src/run_apache.sh 内容如下:

#!/bin/bash
set -e

mkdir -p /run/php /var/run/apache2 /var/lock/apache2

# 先以后台方式拉起 PHP-FPM,再把 Apache 保持在前台作为容器主进程
/usr/sbin/php-fpm8.3 --daemonize

. /etc/apache2/envvars
exec /usr/sbin/apache2 -D FOREGROUND

Dockerfile 主文件内容如下:

FROM public.ecr.aws/ubuntu/ubuntu:24.04

ENV DEBIAN_FRONTEND=noninteractive

RUN apt-get update \
 && apt-get upgrade -y \
 && apt-get install -y --no-install-recommends \
    apache2 \
    php8.3-cli \
    php8.3-fpm \
    php8.3-mysql \
    php8.3-mbstring \
    php8.3-xml \
 && a2enmod proxy_fcgi setenvif rewrite \
 && a2enconf php8.3-fpm \
 && apt-get clean \
 && rm -rf /var/lib/apt/lists/*

ADD src/run_apache.sh /root/
RUN echo "<?php phpinfo(); ?>" > /var/www/html/index.php \
 && chown -R www-data:www-data /var/www \
 && chmod +x /root/run_apache.sh

ENV APACHE_RUN_USER=www-data
ENV APACHE_RUN_GROUP=www-data
ENV APACHE_LOG_DIR=/var/log/apache2

EXPOSE 80

CMD ["/bin/bash", "-c", "/root/run_apache.sh"]

这里选用 public.ecr.aws/ubuntu/ubuntu:24.04 作为基础镜像而不是 Docker Hub 上的 ubuntu:24.04,一方面 Amazon ECR Public 不对拉取施加匿名速率限制,另一方面在 AWS 环境内拉取的延迟与稳定性都更好。基础镜像内容与 Canonical 官方发布的一致。其中 DEBIAN_FRONTEND=noninteractive 用于避免 tzdata 等软件包在安装过程中弹出交互式时区选择而导致构建挂起;apt-get clean 与删除 /var/lib/apt/lists 目录写在同一个 RUN 指令中,是为了让下载的索引与 deb 缓存不进入镜像层,否则即使后续用单独的 RUN 删除,这些文件仍然会留在前一层中占用体积。

构建完成后启动容器,并映射本机 8080 端口做验证:

docker run -d --name php-ub -p 8080:80 php-apache-ubuntu:latest
curl -s -o /dev/null -w "HTTP %{http_code}\n" http://localhost:8080/index.php

返回结果如下:

HTTP 200

进入容器查看进程树,可以确认 Apache 以 PID 1 的身份驻留前台,PHP-FPM 的 master 与 pool 进程在后台正常运行:

UID          PID    PPID  C STIME TTY          TIME CMD
root           1       0  0 15:42 ?        00:00:00 /usr/sbin/apache2 -D FOREGROUND
root           8       1  0 15:42 ?        00:00:00 php-fpm: master process (/etc/php/8.3/fpm/php-fpm.conf)
www-data       9       8  0 15:42 ?        00:00:00 php-fpm: pool www
www-data      10       8  0 15:42 ?        00:00:00 php-fpm: pool www
www-data      11       1  0 15:42 ?        00:00:00 /usr/sbin/apache2 -D FOREGROUND
www-data      12       1  0 15:42 ?        00:00:00 /usr/sbin/apache2 -D FOREGROUND

查询组件版本,返回结果如下:

Server version: Apache/2.4.58 (Ubuntu)
PHP 8.3.6 (cli) (built: Sep  2 2026 12:56:02) (NTS)
PHP 8.3.6 (fpm-fcgi) (built: Sep  2 2026 12:56:02)

访问 index.php 输出的 phpinfo 页面中,Server API 一栏显示为 FPM/FastCGI,说明请求确实经由 proxy_fcgi 模块转发给了 PHP-FPM,而不是由 mod_php 在 Apache 进程内解析:

Server API                          FPM/FastCGI
Configuration File (php.ini) Path   /etc/php/8.3/fpm

本次构建的镜像体积为 289MB(linux/arm64 平台,解压后尺寸)。如果对体积敏感,可以进一步裁剪 php8.3-xml、php8.3-mbstring 等按需扩展,或改用 ubuntu 的 minimal 镜像作为基础层。

需要说明的是,Ubuntu 24.04 软件源中的 PHP 会长期停留在 8.3 分支,仅接收安全补丁而不做大版本跳跃。若业务必须使用 PHP 8.4 或更高版本,则应考虑下一章基于 Amazon Linux 2023 的方案,该系统在同一个官方仓库内同时提供多个 PHP 大版本。


三、基于 Amazon Linux 2023 构建镜像

兼容 CentOS 9 生态的 Amazon Linux 2023 系统,在同一个官方仓库中同时提供 PHP 8.1、8.2、8.3、8.4、8.5 五个大版本,每个版本的支持周期与 PHP 上游社区保持一致,因此不需要额外引入 Remi 等第三方仓库就能获得较新的 PHP 运行环境。软件包采用带版本号的命名方式,例如 php8.4、php8.4-fpm、php8.4-mysqlnd。

这里有一处相对早期版本的重要变化:不带版本号的 php 元包所指向的大版本会随系统的更新而发生变化,早期 AL2023 的 php 指向 8.1,此后逐步前移。为了保证构建结果可复现,避免同一份 Dockerfile 在不同时间构建出不同 PHP 大版本的镜像,应当显式写明版本号,而不是沿用旧写法中的 php。

另一处变化涉及 PHP 的运行模式。AL2023 已经把 PHP-FPM 作为 httpd 解析 PHP 的默认方式,mod_php 被标记为不推荐使用,且仅 php8.3-modphp 一个软件包提供该模块,PHP 8.1、8.2 以及 8.4 之后的版本都不再提供 mod_php。安装 php8.x-fpm 时会自动写入 /etc/httpd/conf.d/php.conf 配置片段,其中通过 SetHandler 将 .php 与 .phar 请求转发到 Unix Socket /run/php-fpm/www.sock,同时把 index.php 加入 DirectoryIndex,因此 Apache 侧同样不需要手工编写转发配置。该片段的关键内容如下:

<IfModule !mod_php.c>
    # Enable http authorization headers
    SetEnvIfNoCase ^Authorization$ "(.+)" HTTP_AUTHORIZATION=$1
    <FilesMatch \.(php|phar)$>
        SetHandler "proxy:unix:/run/php-fpm/www.sock|fcgi://localhost"
    </FilesMatch>
</IfModule>

启动脚本 src/run_apache.sh 内容如下:

#!/bin/bash
set -e

mkdir -p /run/php-fpm /var/run/apache2 /var/lock/apache2

/usr/sbin/php-fpm -D
exec /usr/sbin/httpd -D FOREGROUND

注意这里把 /run/php-fpm 目录的创建动作放在启动脚本而不是 Dockerfile 中。原因是容器运行时 /run 通常会被挂载为 tmpfs,构建阶段在镜像层里创建的该目录在运行时会被覆盖掉,导致 PHP-FPM 因为无法创建 Socket 文件而启动失败。

以下为 Dockerfile 主文件内容:

FROM public.ecr.aws/amazonlinux/amazonlinux:2023

# Install dependencies
RUN dnf -y update \
 && dnf -y install \
    httpd \
    php8.4 \
    php8.4-fpm \
    php8.4-mysqlnd \
    php8.4-mbstring \
    php8.4-opcache \
 && ln -s /usr/sbin/httpd /usr/sbin/apache2 \
 && dnf clean all \
 && rm -rf /var/cache/dnf

# Install app
ADD src/run_apache.sh /root/
RUN echo "<?php phpinfo(); ?>" > /var/www/html/index.php \
 && chown -R apache:apache /var/www \
 && chmod +x /root/run_apache.sh

# Configure apache
ENV APACHE_RUN_USER=apache
ENV APACHE_RUN_GROUP=apache
ENV APACHE_LOG_DIR=/var/log/httpd

EXPOSE 80

# starting script for php-fpm and httpd
CMD ["/bin/bash", "-c", "/root/run_apache.sh"]

其中 ln -s /usr/sbin/httpd /usr/sbin/apache2 这行软链接并非必需,保留它是为了兼容那些按 Debian 系习惯调用 apache2 命令的运维脚本与健康检查逻辑。dnf clean all 与删除 /var/cache/dnf 同样必须与安装命令写在同一个 RUN 指令内,否则元数据缓存会固化在中间层里。另外 ENV 指令建议使用 KEY=VALUE 形式书写,不带等号的旧写法已被 BuildKit 标记为过时语法,在较新的构建器上会产生告警。

构建完成后启动容器,映射本机 8081 端口做验证:

docker run -d --name php-al -p 8081:80 php-apache-al2023:latest
curl -s -o /dev/null -w "HTTP %{http_code}\n" http://localhost:8081/index.php

返回结果如下:

HTTP 200

需要提醒的是,AL2023 基础镜像属于精简发行版,默认没有安装 procps-ng,容器内不存在 ps 命令,直接执行会报 executable file not found 错误。排查进程时可以通过遍历 /proc 目录完成,返回结果如下:

1      /usr/sbin/httpd -D FOREGROUND
9      php-fpm: master process (/etc/php-fpm.conf)
10     php-fpm: pool www
11     php-fpm: pool www
15     /usr/sbin/httpd -D FOREGROUND
16     /usr/sbin/httpd -D FOREGROUND

查询组件版本,返回结果如下:

Server version: Apache/2.4.68 (Amazon Linux)
Server built:   Jun  8 2026 00:00:00
PHP 8.4.25 (cli) (built: Aug 25 2026 18:15:03) (NTS gcc aarch64)
PHP 8.4.25 (fpm-fcgi) (built: Aug 25 2026 18:15:03) (NTS gcc aarch64)

访问 index.php 输出的 phpinfo 页面,Server API 一栏显示为 FPM/FastCGI,确认请求由 PHP-FPM 处理:

Server API                          FPM/FastCGI
Configuration File (php.ini) Path   /etc

本次构建的镜像体积为 389MB(linux/arm64 平台,解压后尺寸),比上一章的 Ubuntu 镜像略大,差异主要来自 dnf -y update 引入的系统组件更新以及 opcache、mysqlnd 等扩展。若要把 PHP 切换到其他大版本,只需把 Dockerfile 中的 php8.4 系列包名整体替换为 php8.3 或 php8.5,启动脚本无须调整,因为 AL2023 的 PHP-FPM 可执行文件路径不带版本号后缀。

下面进入镜像构建与上传环节。


四、发起镜像构建

由此可按照这篇文章构建容器镜像,然后上传到AWS ECR,即可用于ECS和EKS容器服务。


最后修改于 2026-09-21