完全静态构建产物必须满足:file显示“statically linked”,ldd报“not a dynamic executable”,且能在scratch容器中直接运行;否则仍依赖glibc,非真正零依赖。

完全静态构建产物是 not a dynamic executable
用 dunglas/frankenphp:static-builder-musl 构建出的二进制,执行 file ./frankenphp 应返回类似 ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), statically linked;运行 ldd ./frankenphp 必须报错 not a dynamic executable。这是唯一能确认“真正零依赖”的方式。
一旦 ldd 显示任何 libc.so.6 或 libpthread.so.0,说明构建环境用了 glibc(比如在 Ubuntu 宿主机上直接跑 make),哪怕文件名带 static 也是假静态——它仍需目标机器装对应版本的 glibc,部署到 Alpine 或某些精简发行版会直接 exec format error 或 no such file。
部分静态 = PHP 运行时静态,但 Caddy 或系统库动态链接
FrankenPHP 官方不提供、也不支持所谓“部分静态”构建模式。所有声称“只静态 PHP、Caddy 动态”的做法,本质是绕过官方构建流程,例如:
- 在 glibc 环境下编译 PHP 模块,再手动拼接进 Caddy 二进制 —— 结果仍是动态链接,
ldd必然显示依赖 - 用
go build -ldflags="-linkmode external -extldflags '-static'"强行链接 Go 部分 —— 但 PHP 的 libphp 是 C 编译的,musl 工具链缺失时无法真正静态化 - 误把 Docker 多阶段构建中 “FROM scratch” 当作静态 ——
scratch镜像里没 libc,但若二进制本身动态链接,运行时照样失败
这类产物看似体积小,实则丧失跨环境部署能力:在 CentOS 上能跑,换到 Alpine 就挂;本地测试 OK,上线到 Kubernetes initContainer 就报 standard_init_linux.go:228: exec user process caused: no such file。
PHP_EXTENSIONS 列表决定哪些扩展被真正静态编译进二进制
扩展不是“启用”就行,而是必须在构建阶段显式安装,否则二进制里压根没有那段代码。比如:
-
RUN docker-php-ext-install opcache pdo_mysql→ opcache 和 pdo_mysql 的符号会被打包进最终二进制 - 只写
extension=opcache.so在php.ini里 → 启动时找不到文件,报Failed loading opcache.so: Error loading shared library - 同时装
pdo_mysql和pdo_sqlite→ 可能因符号重复导致运行时报undefined symbol: sqlite3_libversion
注意:mbstring、json、curl 是 FrankenPHP 内置的,无需 docker-php-ext-install;但 iconv、ctype 这类基础扩展,musl 下默认不启用,漏掉会导致 Laravel 的 Str::slug() 或 Composer 自动加载失败。
验证静态性的三个命令缺一不可
光看文件大小或启动成功不说明问题。必须连续执行:
-
file ./frankenphp→ 确认含statically linked字样 -
ldd ./frankenphp→ 必须输出not a dynamic executable -
./frankenphp version→ 在无网络、无额外依赖的最小容器(如FROM scratch)中能直接运行
最容易被忽略的是第三步:很多构建产物在宿主机 Docker Desktop 里能跑,是因为宿主机内核/库做了兼容层;一旦扔进 scratch 或 alpine:latest,立刻暴露动态链接缺陷。真正的静态,是连 libc 都不认的硬核单体。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











