FrankenPHP Docker 镜像完全指南:从自定义构建、扩展安装到生产加固
【免费下载链接】frankenphp🧟 The modern PHP app server项目地址: https://gitcode.com/GitHub_Trending/fr/frankenphp
本文围绕 FrankenPHP 官方 Docker 镜像的使用展开,覆盖镜像标签体系、自定义 Dockerfile 构建、PHP 扩展与 Caddy 模块安装、worker 模式、开发期卷挂载、非 root 运行以及基于 distroless 的生产加固等完整实战方案。读完本文,你将能够基于 Dockerfile 与 caddy/frankenphp/Caddyfile 的仓库实现,独立构建一个贴合自身业务需求、可安全投产的 FrankenPHP 容器镜像。
FrankenPHP 官方镜像的构成与标签体系
FrankenPHP 的 Docker 镜像基于 PHP 官方镜像构建,并针对主流 CPU 架构提供Debian与Alpine Linux两种变体。官方建议优先选用 Debian 变体,因为其 glibc 生态兼容性更好、调试与排障更便利。
镜像同时提供PHP 8.2、8.3、8.4 与 8.5四个 PHP 大版本的变体。这一矩阵在仓库的 docker-bake.hcl 中通过PHP_VERSION = "8.2,8.3,8.4,8.5"变量声明,并以DEFAULT_PHP_VERSION = "8.5"指定默认版本。
理解镜像标签(Tag)
镜像标签遵循统一模式:
dunglas/frankenphp:<frankenphp-version>-php<php-version>-<os><frankenphp-version>与<php-version>分别为 FrankenPHP 与 PHP 的版本号,粒度覆盖主版本(如1)、次版本(如1.2)直至补丁版本(如1.2.3);<os>取值如下:trixie:基于 Debian Trixie;bookworm:基于 Debian Bookworm;alpine:基于最新稳定版 Alpine Linux。
docker-bake.hcl 中的构建矩阵显示,Debian 系(trixie/bookworm)镜像覆盖linux/amd64、linux/386、linux/arm/v7与linux/arm64,而 Alpine 变体额外支持linux/arm/v6。所有 tag 的实际列表可在 Docker Hub 的镜像 tags 页面浏览。
说明:本仓库为 GitHub 热门项目镜像,实际拉取镜像请使用
docker pull dunglas/frankenphp并从官方 Docker Hub 获取,具体 tag 以线上列表为准。
快速上手:构建并运行你的第一个 FrankenPHP 镜像
在项目根目录创建Dockerfile:
FROM dunglas/frankenphp COPY . /app/public然后依次执行构建与运行命令:
docker build -t my-php-app . docker run -it --rm --name my-running-app my-php-app容器启动后即默认监听 HTTP 80 端口、HTTPS 443 端口及 HTTP/3 的443/udp端口(这三个端口在 Dockerfile 中通过EXPOSE声明,另暴露 2019 管理端口)。FrankenPHP 会自动为配置的主机名签发本地 HTTPS 证书,因此直接通过浏览器访问即获得完整的 HTTPS 体验。
镜像内的默认入口行为定义在 Dockerfile 与 alpine.Dockerfile 中:通过sed将 PHP 官方镜像的docker-php-entrypoint中的php替换为frankenphp run,并以CMD ["--config", "/etc/frankenphp/Caddyfile", "--adapter", "caddyfile"]作为默认启动参数。
镜像内的目录约定
从构建文件可知,镜像内约定如下路径:
/app/public:站点根目录,应用代码应挂载或拷贝到这里;/etc/caddy/Caddyfile与/etc/frankenphp/Caddyfile:主配置文件(后者是前者的硬链接);/config与/data:分别对应XDG_CONFIG_HOME与XDG_DATA_HOME,存放 Caddy 配置与证书数据(见 Dockerfile)。
通过环境变量调整 FrankenPHP Docker 配置
为方便使用,官方镜像内置了一份包含常用环境变量的默认Caddyfile,其源码即仓库中的 caddy/frankenphp/Caddyfile。该文件利用 Caddy 的占位符语法,将以下环境变量作为配置入口:
| 环境变量 | 作用 | 默认值 |
|---|---|---|
SERVER_NAME | 设置站点监听地址与 TLS 证书主机名 | localhost |
SERVER_ROOT | 设置站点根目录 | public/ |
FRANKENPHP_CONFIG | 向全局frankenphp指令注入配置(如 worker 脚本) | 空 |
CADDY_GLOBAL_OPTIONS | 注入 Caddy 全局选项 | 空 |
CADDY_SERVER_EXTRA_DIRECTIVES | 注入站点块内的附加指令 | 空 |
CADDY_EXTRA_CONFIG | 注入 Caddyfile 顶层附加配置 | 空 |
例如启用 Caddy 调试模式只需:
docker run -v $PWD:/app/public \ -e CADDY_GLOBAL_OPTIONS=debug \ -p 80:80 -p 443:443 -p 443:443/udp \ dunglas/frankenphp默认 Caddyfile 还开启了encode zstd br gzip压缩,并预留了 Mercure(注释状态)与 Vulcain 模块位,以及Caddyfile.d/*.caddyfile目录的自动导入,方便用户以追加文件的方式扩展配置而不修改主文件。
安装额外的 PHP 扩展
基础镜像内置了docker-php-extension-installer脚本,路径为/usr/local/bin/install-php-extensions(安装逻辑见 Dockerfile)。添加扩展非常直接:
FROM dunglas/frankenphp # 在此添加其他扩展: RUN install-php-extensions \ pdo_mysql \ gd \ intl \ zip \ opcache该脚本会自动处理扩展依赖的编译与安装,无需手工docker-php-ext-configure/docker-php-ext-install。安装后的扩展会被放置到 PHP 官方镜像约定的extension_dir,即/usr/local/lib/php/extensions/no-debug-zts-<YYYYMMDD>/(参见 docs/config.md 的 Docker 配置位置说明)。
此外,docs/config.md 建议在需要自定义 PHP 运行时行为时,从官方模板复制一份php.ini:
FROM dunglas/frankenphp # 生产环境: RUN cp $PHP_INI_DIR/php.ini-production $PHP_INI_DIR/php.ini # 或开发环境: RUN cp $PHP_INI_DIR/php.ini-development $PHP_INI_DIR/php.ini安装自定义 Caddy 模块(xcaddy + builder 镜像)
FrankenPHP 构建于 Caddy 之上,因此所有 Caddy 模块都可与 FrankenPHP 一起使用。官方提供名为builder的构建镜像(包含编译好的libphp及完整 Go 工具链),配合 xcaddy 可以轻松定制二进制:
FROM dunglas/frankenphp:builder AS builder # 将 xcaddy 复制进 builder 镜像 COPY --from=caddy:builder /usr/bin/xcaddy /usr/bin/xcaddy # 构建 FrankenPHP 必须启用 CGO RUN CGO_ENABLED=1 \ XCADDY_SETCAP=1 \ XCADDY_GO_BUILD_FLAGS="-ldflags='-w -s' -tags=nobadger,nomysql,nopgx" \ CGO_CFLAGS=$(php-config --includes) \ CGO_LDFLAGS="$(php-config --ldflags) $(php-config --libs)" \ xcaddy build \ --output /usr/local/bin/frankenphp \ --with github.com/dunglas/frankenphp=./ \ --with github.com/dunglas/frankenphp/caddy=./caddy/ \ --with github.com/dunglas/caddy-cbrotli \ # Mercure 与 Vulcain 已包含在官方构建中,可按需移除 --with github.com/dunglas/mercure/caddy \ --with github.com/dunglas/vulcain/caddy # 在此添加其他 Caddy 模块 FROM dunglas/frankenphp AS runner # 用包含自定义模块的二进制替换官方二进制 COPY --from=builder /usr/local/bin/frankenphp /usr/local/bin/frankenphp要点说明:
builder镜像内置了编译好的libphp共享库,因此无需在构建阶段重新编译 PHP;所有 FrankenPHP/PHP 版本、Debian 与 Alpine 变体均提供对应的builder镜像;CGO_ENABLED=1是硬性要求——FrankenPHP 通过 CGO 将 PHP 以共享库方式链接进 Go 二进制(参见 docs/compile.md 中 xcaddy 构建一节);- 官方二进制默认已内置 Mercure 与 Vulcain 模块,上例只是展示保留它们的方式,可在
--with列表中自由增删; XCADDY_GO_BUILD_FLAGS中的-tags=nobadger,nomysql,nopgx用于关闭 Caddy 内置存储模块以减少体积。
提示:若使用 Alpine Linux 运行 Symfony 应用,musl libc 下可能需要增大默认栈大小,否则可能出现
PHP Fatal error: Maximum call stack size of 83360 bytes reached during compilation错误。具体做法参见 docs/it/compile.md(对应英文文档 docs/compile.md),即将栈大小通过-extldflags '-Wl,-z,stack-size=0x80000'调大。Alpine 官方镜像的构建文件 alpine.Dockerfile 中正包含这一链接参数。
默认启用 worker 模式
设置FRANKENPHP_CONFIG环境变量即可让容器以 worker 脚本模式启动,应用只需引导一次并常驻内存,请求响应延迟可降至毫秒级(原理详见 docs/worker.md):
FROM dunglas/frankenphp # ... ENV FRANKENPHP_CONFIG="worker ./public/index.php"该值会被默认 Caddyfile 注入到全局frankenphp指令中。worker 模式同样支持指定启动线程数(默认每 CPU 2 个),例如ENV FRANKENPHP_CONFIG="worker ./public/index.php 42",可参考 docs/worker.md 的说明。
开发阶段使用卷挂载
将宿主机中的应用源码目录以卷方式挂载进容器,即可实现修改即生效的开发循环:
docker run -v $PWD:/app/public -p 80:80 -p 443:443 -p 443:443/udp --tty my-php-app提示:
--tty选项会让容器输出人类可读的日志,而非 JSON 格式日志。
使用 Docker Compose 时,推荐配置如下:
# compose.yaml services: php: image: dunglas/frankenphp # 如需使用自定义 Dockerfile,取消注释下面一行 #build: . # 生产环境运行请取消注释下面一行 # restart: always ports: - "80:80" # HTTP - "443:443" # HTTPS - "443:443/udp" # HTTP/3 volumes: - ./:/app/public - caddy_data:/data - caddy_config:/config # 生产环境请注释下面一行,开发时提供可读日志 tty: true # Caddy 证书与配置所需的卷 volumes: caddy_data: caddy_config:这里caddy_data与caddy_config两个命名卷分别对应镜像内的/data与/config(即XDG_DATA_HOME与XDG_CONFIG_HOME),用于持久化 Caddy 自动签发的 TLS 证书与运行时配置,避免容器重建后重新签发。
以非 root 用户运行
FrankenPHP 支持在 Docker 中以非 root 用户运行。以下 Dockerfile 创建应用用户并保留绑定特权端口的 capability:
FROM dunglas/frankenphp ARG USER=appuser RUN <<-EOF # 基于 Alpine 的发行版请使用 "adduser -D ${USER}" useradd ${USER} # 赋予绑定 80 与 443 端口的额外能力 setcap CAP_NET_BIND_SERVICE=+eip /usr/local/bin/frankenphp # 授予 /config/caddy 与 /data/caddy 写权限 chown -R ${USER}:${USER} /config/caddy /data/caddy EOF USER ${USER}无 capability 运行
即使以非 root 运行,FrankenPHP 仍需要CAP_NET_BIND_SERVICE能力才能绑定 80、443 等特权端口。若改用非特权端口(1024 及以上),则可完全去掉 capability:
FROM dunglas/frankenphp ARG USER=appuser RUN <<-EOF # 基于 Alpine 的发行版请使用 "adduser -D ${USER}" useradd ${USER} # 移除默认能力 setcap -r /usr/local/bin/frankenphp # 授予 /config/caddy 与 /data/caddy 写权限 chown -R ${USER}:${USER} /config/caddy /data/caddy EOF USER ${USER}随后通过SERVER_NAME环境变量指定非特权端口,例如:
docker run -e SERVER_NAME=:8000 -p 8000:8000 my-php-app注意此时容器内的服务端口变为 8000,映射到宿主机的-p 8000:8000。默认镜像二进制本身带有cap_net_bind_service=+ep能力,这是由官方构建流程在 Dockerfile 与 alpine.Dockerfile 中通过setcap设置的。
镜像更新机制与开发版本
更新时机
FrankenPHP 官方 Docker 镜像在以下两种情况下自动重建:
- 每当发布新版本(tag 新 release)时;
- 每天 UTC 时间 4:00,若官方 PHP 镜像有新版本可用时。
前者保证功能迭代及时跟上,后者保证安全补丁(如 PHP 安全修复)能在 24 小时内落地。
开发版本(dev)
每日构建的开发版本发布在独立的dunglas/frankenphp-dev仓库中:每当 GitHub 仓库的main分支收到新提交,就会触发一次新构建。其标签约定为:
latest*系列标签指向main分支最新提交(head);- 形如
sha-<git-commit-hash>的标签对应具体提交哈希,便于复现某个快照。
仓库中的 dev.Dockerfile 展示了开发镜像的构建方式:它从 PHP 8.5 源码编译带调试符号的libphp,并预装 gdb、valgrind、neovim 等调试工具,主要用于 FrankenPHP 本身的开发调试。
生产加固:distroless 与 Docker 加固镜像
为进一步缩小攻击面与镜像体积,可以基于 Google distroless 或 Docker 加固镜像构建最终镜像。
[!WARNING] 这类最小化基础镜像不包含 shell 与包管理器,调试难度显著增加,因此仅建议在安全优先级极高的生产环境使用。
由于最小基础镜像没有包管理器,安装 PHP 扩展必须在中间构建阶段完成,再手工拷贝扩展及其共享库依赖:
FROM dunglas/frankenphp AS builder # 在此添加 PHP 扩展 RUN install-php-extensions pdo_mysql pdo_pgsql #... # 将 frankenphp 与所有已安装扩展的共享库复制到临时位置 # 也可以手工分析 frankenphp 二进制与每个扩展 .so 文件的 ldd 输出来完成此步骤 RUN <<-EOF apt-get update apt-get install -y --no-install-recommends libtree mkdir -p /tmp/libs for target in $(which frankenphp) \ $(find "$(php -r 'echo ini_get("extension_dir");')" -maxdepth 2 -name "*.so"); do libtree -pv "$target" 2>/dev/null | grep -oP '(?:── )\K/\S+(?= \[)' | while IFS= read -r lib; do [ -f "$lib" ] && cp -n "$lib" /tmp/libs/ done done EOF # 使用与 builder 相同 Debian 版本的 Distroless 基础镜像 FROM gcr.io/distroless/base-debian13 # Docker 加固镜像替代方案 # FROM dhi.io/debian:13 COPY --from=builder /usr/local/bin/frankenphp /usr/local/bin/frankenphp COPY --from=builder /usr/local/lib/php/extensions /usr/local/lib/php/extensions COPY --from=builder /tmp/libs /usr/lib COPY --from=builder /usr/local/etc/php/conf.d /usr/local/etc/php/conf.d COPY --from=builder /usr/local/etc/php/php.ini-production /usr/local/etc/php/php.ini # 配置与数据目录必须对非 root 用户可写,即使根文件系统为只读 ENV XDG_CONFIG_HOME=/config XDG_DATA_HOME=/data COPY --from=builder --chown=nonroot:nonroot /data /data COPY --from=builder --chown=nonroot:nonroot /config /config # 复制应用(保持 root 所有权)与 Caddyfile COPY . /app COPY Caddyfile /etc/caddy/Caddyfile USER nonroot WORKDIR /app ENTRYPOINT ["/usr/local/bin/frankenphp", "run", "--config", "/etc/caddy/Caddyfile"]该方案的关键步骤与原理:
- 多阶段构建:
builder阶段复用官方镜像完成扩展安装,并借助libtree递归解析出frankenphp二进制与所有扩展.so文件依赖的动态库,统一收集到/tmp/libs; - 文件搬运:将二进制、PHP 扩展目录、
conf.d配置、生产版php.ini以及收集到的动态库分别复制到 distroless 镜像的对应位置; - 只读根文件系统适配:通过
XDG_CONFIG_HOME=/config与XDG_DATA_HOME=/data将 Caddy 的运行时写入重定向到挂载卷,且/data、/config以nonroot用户所有,保证非 root 可写; - 非 root 与固定入口:最终以
nonroot用户运行,ENTRYPOINT直接指向frankenphp run --config /etc/caddy/Caddyfile,无 shell 可用、无提权路径,攻击面最小化。
注意:由于运行时无setcap工具,此镜像依赖非特权端口(通过SERVER_NAME配置)或由容器运行时注入 capability,这是 distroless 方案与常规镜像在端口绑定方式上的主要差异。
常见问题与排查建议
- 想确认镜像内的版本信息:执行
docker run --rm dunglas/frankenphp frankenphp version与frankenphp build-info(官方构建流程 Dockerfile 在构建时即运行这两个命令校验产物); - 健康检查:官方镜像内置
HEALTHCHECK,通过curl -f http://localhost:2019/metrics探测 Caddy 管理端点(见 Dockerfile),自定义镜像时建议保留或参照实现; - 修改默认站点行为:优先通过
SERVER_NAME、SERVER_ROOT、FRANKENPHP_CONFIG等环境变量注入,或向Caddyfile.d/目录追加.caddyfile文件,避免直接修改主配置; - 非特权端口与 TLS:改用
:8000这类非特权端口后,默认自动 HTTPS 行为会相应调整,开发环境如需完全禁用 HTTPS,可将SERVER_NAME设为http://或:80(参见 docs/config.md)。
通过以上从基础运行到生产加固的完整链路,你可以根据业务场景自由组合官方镜像的各个能力点,构建出安全、可维护且性能优异的 FrankenPHP 容器化应用。
【免费下载链接】frankenphp🧟 The modern PHP app server项目地址: https://gitcode.com/GitHub_Trending/fr/frankenphp
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考