FrankenPHP 的 GitHub Actions 镜像构建与发布流水线全解析
【免费下载链接】frankenphp🧟 The modern PHP app server项目地址: https://gitcode.com/GitHub_Trending/fr/frankenphp
本指南以 FrankenPHP 官方仓库中的 docs/tr/github-actions.md 为核心,系统讲解该仓库如何利用 GitHub Actions 自动完成 Docker 镜像的编译、测试与部署:包括仓库 Secrets 的配置方法、Pull Request / Fork、合并 main、打版本标签三种典型触发场景,以及pr-x、main、v1.2.3、latest等标签的生成规则。读完本文,你将掌握如何为 FrankenPHP 项目(或任何基于该流水线的派生仓库)配置一套完整的 Docker 镜像 CI/CD 流程,并理解其背后 .github/workflows/docker.yaml 与 docker-bake.hcl 的底层实现逻辑。
流水线概览:一次提交如何变成 Docker 镜像
FrankenPHP 的镜像流水线以 GitHub Actions 为执行引擎,目标是把仓库源码编译成可直接运行的 Docker 镜像并推送到 Docker Hub 上的dunglas/frankenphp仓库。整个流程遵循"先构建、再测试、后推送"的原则:
- 代码变更(Pull Request、Fork、合并、版本标签)触发工作流;
- GitHub Actions 使用 Dockerfile / alpine.Dockerfile 配合 Docker Buildx 多架构构建镜像;
- 构建产物先在本地产出并执行全量测试;
- 构建与测试全部通过后,镜像才被推送并打上对应语义的标签。
从当前仓库的 .github/workflows/docker.yaml 可以看到,工作流同时监听多种事件源(第 6-32 行):指向main分支的pull_request、main分支的push、v*.*.*格式的标签推送、手动触发的workflow_dispatch,以及每日凌晨 4 点的定时重建schedule。这意味着镜像不是"发版才构建",而是随代码演进持续维护。
前置准备:在仓库设置中配置 Secrets
要让流水线能够登录并推送镜像到 Docker Registry,必须在仓库的Settings → Secrets and variables → Actions中提前配置以下四个敏感值(原文档核心内容):
| Secret 名称 | 用途 | 示例值 |
|---|---|---|
REGISTRY_LOGIN_SERVER | 目标 Docker Registry 地址 | docker.io |
REGISTRY_USERNAME | 登录 Registry 使用的用户名 | dunglas |
REGISTRY_PASSWORD | 登录密码(推荐使用 Access Token,而非账号密码) | 形如dckr_pat_...的访问令牌 |
IMAGE_NAME | 镜像的完整名称 | dunglas/frankenphp |
这四个值分别回答了推送镜像时需要回答的四个问题:推到哪、以谁的身份、用什么凭证、叫什么名字。它们都是运行时敏感信息,应作为 GitHub Secrets 存储,绝不能硬编码进工作流文件或提交到代码库。
官方工作流中的实际凭证用法
从源码角度印证,当前仓库的 docker.yaml 在登录 Docker Hub 时实际引用的是vars.DOCKERHUB_USERNAME与secrets.DOCKERHUB_TOKEN(第 70-71 行):
- name: Login to DockerHub uses: docker/login-action@... with: username: ${{ vars.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }}也就是说,如果你在官方仓库的镜像基础上派生(Fork)并自建发布,需要把上文表格中的四个值替换为与你自己的 Docker 账号对应的真实值;而官方仓库自身的流水线则以 DOCKERHUB 前缀的变量/密文承载同样的信息。此外,登录步骤还通过条件判断限制了执行范围:仅在非 PR 场景,或 PR 来源仓库与主仓库一致时才执行登录(github.event.pull_request.head.repo.full_name == github.repository),避免不可信的 Fork 代码窃取凭据。
场景一:Pull Request 或 Fork 触发的预发布构建
按照原文档的描述,仓库会在每次被批准的 Pull Request 或 Fork 之后自动执行一次构建。具体行为如下:
- 提交者创建一个 Pull Request,或将仓库 Fork 后推送自己的分支;
- GitHub Actions 随即开始构建镜像,并运行全部测试;
- 构建与测试全部成功后,镜像被推送到目标 Registry,标签格式为
pr-x,其中x为 PR 编号。
这套机制的价值在于:任何未经合并的代码改动都能以pr-x标签生成一份可拉取验证的临时镜像,维护者与贡献者可以直接docker pull该镜像做集成验证,而不必等待代码合入主干。
源码视角:PR 构建如何运行测试
在 docker.yaml 中,PR 场景(push输出为 false 时)会在镜像构建完成后直接docker run该镜像执行测试(第 213-225 行):
docker run --platform="${PLATFORM}" --rm \ "<构建出的镜像ID>" \ sh -c "./go.sh test ${RACE} -v $(./go.sh list ./... | grep -v .../internal/testext | grep -v .../internal/extgen | tr '\n' ' ') && cd caddy && ../go.sh test ${RACE} -v ./..."值得注意的细节:
- 测试通过
./go.sh test驱动,覆盖仓库根目录下除internal/testext、internal/extgen之外的所有 Go 包,并额外进入caddy目录测试 Caddy 模块; - 在
linux/amd64平台上还会追加-race参数启用 Go 竞态检测器(第 110-112 行); - 对 fork 来源的 PR,流水线只构建不推送(
load: true),测试通过后即完成使命。
场景二:合并到 main 分支后的正式发布
当 Pull Request 被合并后,流水线进入正式发布路径:
- GitHub Actions 再次运行测试并构建全新镜像;
- 构建成功后,Registry 中的
main标签被更新,指向最新合并产物。
main标签代表"主干最新可用版本",是日常开发中拉取最新 FrankenPHP 镜像最常用的标签。从 docker-bake.hcl 的tag()函数(第 55-63 行)可以看到,main场景下流水线还会同时生成latest、latest-php{版本}-{OS}等别名标签,方便不同需求按需拉取。
镜像命名与多架构矩阵
合并触发的构建并非单一架构,而是完整的多架构矩阵。docker-bake.hcl 第 100-126 行定义了构建矩阵:
- 操作系统:
trixie(Debian 最新)、bookworm(Debian 稳定版)、alpine三种基础镜像; - PHP 版本:默认覆盖 8.2/8.3/8.4/8.5(
PHP_VERSION变量,第 9-11 行); - 目标阶段:
builder(编译工具链)与runner(运行时镜像)双阶段; - 平台:
linux/amd64、linux/386、linux/arm/v7、linux/arm64(Alpine 额外支持linux/arm/v6,第 114-126 行)。
构建任务会按平台分发到ubuntu-24.04或ubuntu-24.04-arm运行器(docker.yaml 第 100 行),最终在pushjob 中通过docker buildx imagetools create将各平台产物合并为多架构 Manifest(第 259-265 行),实现"一个标签、多架构拉取"。
场景三:创建版本标签的发布流程
FrankenPHP 的版本发布完全由 Git 标签驱动,这是原文档描述的第三条触发路径:
- 维护者在仓库中创建一个新标签(如
v1.2.3); - GitHub Actions 构建镜像并运行全部测试;
- 构建成功后,镜像按标签名推送到 Registry——以
v1.2.3为例,会同时生成v1.2.3(完整版本)和v1.2(主次版本)两个标签; latest标签同步更新到该版本。
标签语义化背后的实现
这套"一个版本、多个标签"的规则由 docker-bake.hcl 中的semver()函数实现(第 73-88 行)。它用正则解析v1.2.3形式的版本号,拆分为 major / minor / patch 三部分,进而生成:
v1.2.3(完整版本号)v1.2(major.minor,便于用户锁定次版本)latest(仅当该版本为默认 PHP 版本且为基础 OS 时生成)
同时,非 tag 场景下还会附加sha-{commit前7位}格式的不可变标签(第 130 行),保证任意一次提交都能被精确追溯。所有标签都经过clean_tag()清洗(第 68-71 行),确保符合 Docker 标签的合法字符集,并将非法字符替换为-。
从标签到正式 Release 的完整链路
版本标签触发的不止是镜像构建。.github/workflows/release.yaml 展示了从标签到正式发布的完整编排:以workflow_dispatch传入语义化版本号,刷新 PGO 性能剖析文件、升级caddy/go.mod依赖、通过 GitHub API 创建v{version}与caddy/v{version}双标签、起草 GitHub Release 草稿,并向static.yaml、docker.yaml、windows.yaml三个下游工作流派发构建任务(第 362-379 行)。其中static.yaml负责产出 Linux/macOS 静态二进制并上传至 Release 资产。
镜像构建的底层支撑
双 Dockerfile 策略
仓库根据运行目标提供两套构建文件:
- Dockerfile:基于 Debian(trixie/bookworm)的通用镜像,
FROM php-base AS common后安装运行依赖、复制 caddy/frankenphp/Caddyfile 作为默认配置,并以frankenphp run作为入口命令(第 25 行); - alpine.Dockerfile:基于 Alpine 的精简镜像,额外支持
linux/arm/v6等平台。
两条路径都由docker-bake.hcl的defaulttarget 统一驱动,通过dockerfile = os == "alpine" ? "alpine.Dockerfile" : "Dockerfile"动态选择(第 111 行),并注入FRANKENPHP_VERSION构建参数。
可复现构建与安全加固
从 docker-bake.hcl 可以看到流水线对镜像质量的两项硬性要求:
- 可复现性:镜像标签记录
org.opencontainers.image.created、version、revision等 OCI 标签(第 134-146 行),并通过BASE_FINGERPRINT记录基础镜像指纹,每日定时重建时只重建基础镜像发生变化的变体,避免无意义的全量构建; - 供应链安全:构建过程使用
secret = ["id=github-token,env=GITHUB_TOKEN"](第 150 行)以 Secret 方式注入 GitHub Token,且 docker.yaml 中pull_request触发被限制在main分支并做路径过滤(仅docker-bake.hcl、**Dockerfile、**cgo.go、**.c/**.h/**.sh/**.stub.php等影响构建的文件变更才触发,第 10-18 行),既节省算力也缩小了攻击面。
常见问题与排查要点
基于上述源码结构,在使用这套流水线时建议关注以下几点:
- PR 不推送镜像:从 fork 发起的 PR 只会构建并测试、不会推送(避免恶意代码污染官方镜像),如需验证可等待维护者合并后拉取
main标签,或在自建仓库中另行配置; - Secrets 名称必须与工作流一致:官方流水线实际读取
DOCKERHUB_USERNAME/DOCKERHUB_TOKEN,文档中的REGISTRY_*四项适用于自行编写的通用模板——fork 自建时务必核对工作流实际引用的变量名; - 标签格式约束:版本标签必须形如
v*.*.*(如v1.2.3),其他格式不会触发发布路径;非语义化分支则生成sha-{7位提交号}标签; - 平台覆盖差异:
arm/v6仅 Alpine 镜像支持,Debian 系列不包含该平台,拉取时需按需选择。
总结
FrankenPHP 的 GitHub Actions 流水线是一个"触发事件驱动 + Buildx 多架构构建 + 语义化标签推送"的完整范例:通过四个 Secrets 完成 Registry 认证,通过 PR / 合并 / 标签三类事件分别产出pr-x、main与v1.2.3+latest系列标签,并在推送前以真实镜像运行全量 Go 测试作为质量门禁。无论是想深入理解镜像 CI 设计,还是计划基于 FrankenPHP 搭建自己的发布流水线,都可以直接参考 .github/workflows/docker.yaml、.github/workflows/release.yaml 与 docker-bake.hcl 这三份核心文件进行改造复用。
【免费下载链接】frankenphp🧟 The modern PHP app server项目地址: https://gitcode.com/GitHub_Trending/fr/frankenphp
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考