换了基础镜像,apk 源还停在老版本
问题
我在给一个 PHP 后端服务做版本升级,基础镜像从旧的 7.4 换到 8.4.27。镜像换完了,Dockerfile 里还有一段是指定 Alpine 软件源地址的(为了在国内拉包快一点),一直用的是同一个仓库地址,没动过。
重建镜像的时候,构建在装编译依赖那一步直接挂了。
现象
报错是装包的时候 404:
musl-1.2.5-r12: HTTP 404
然后整条 apk add 命令退出码 2,后面全断。
看着挺莫名其妙:装的是编译工具链(musl-dev、libc-dev 这类),报错的却是系统 C 库本身。而且同样的包我本地装从来没 404 过。
怎么解决
先说结论:基础镜像和软件源版本对不上。
我之前只查了 Dockerfile 里写的仓库地址,没查基础镜像实际是哪个 Alpine 版本。查完发现:
php:8.4.27-fpm-alpine这个 tag 底层是 Alpine 3.24,自带的是musl 1.2.6-r3;- 我 Dockerfile 里钉死的仓库是 v3.21,那个仓库里的
musl是1.2.5。
musl 就是 Alpine 用的 C 库。装 musl-dev 的时候,apk 发现当前系统的 musl 是 1.2.6-r3,但仓库源里能提供的 musl-dev 是基于 1.2.5 的——版本对不上,apk 就打算把系统的 musl 一起降到 1.2.5 去过。
问题就出在这儿:它要降到的那个具体版本 1.2.5-r12,已经从仓库里被清理掉了。Alpine 仓库会定期把旧修订版 prune 掉,只留最新的。所以 apk 去找,发现文件不在,直接 404。
为了确认,我把几个仓库的 musl 版本都查了一遍:
| 仓库 | musl 版本 |
|---|---|
| 官方 / 阿里云 v3.21 | 1.2.5 |
| 官方 / 阿里云 v3.22 | 1.2.5 |
| 官方 / 阿里云 v3.23 | 1.2.5 |
| 官方 v3.24 | 1.2.6-r3(和基础镜像完全一致) |
| 阿里云 v3.24 | 1.2.6-r2(同一个大版本,文件在,装得上) |
这张表一说就清楚了:只要仓库停在 3.21/3.22/3.23,它给的 musl 就是 1.2.5,而基础镜像要的是 1.2.6-r3,apk 必然尝试降级 → 去找 1.2.5-r12 → 已经被清掉 → 404。
中间我先用了临时办法:把 Dockerfile 里那行指定仓库地址注释掉,直接用基础镜像自带的默认源(官方 CDN 的 Alpine 3.24,musl 1.2.6-r3,和镜像完全一致),重新构建就过了。
但这不是长久之计——国内拉官方源会慢,甚至不稳。最终改法是把仓库版本钉到 v3.24,也就是和基础镜像实际版本对齐。注意阿里云 v3.24 给的是 1.2.6-r2,比镜像里的 r3 低一个修订号,apk 依然会想”降级”,但那个文件在仓库里存在,所以装得上、不会报错。
结论
这次踩的坑,核心是两处独立配置被我当成了同一件事:基础镜像的 tag,和软件源地址的版本号。它们写在 Dockerfile 的不同地方,改一个不会提醒你改另一个。
两个能带走的做法:
- 换基础镜像 tag 之后,第一件事是核对它底层的基础系统版本,然后把软件源地址一起对齐。查基础镜像实际版本有个笨办法但很准:进容器跑
cat /etc/alpine-release,或者apk --version看它默认指向哪个仓库。 - “钉死版本”不等于安全,钉对了才安全。钉死仓库地址本身是好习惯,但钉的值必须和基础镜像对得上。要么干脆不钉,用基础镜像自带的源(最省心,天然不会错配),要么钉到基础镜像的实际版本。
另外有一点值得记:apk 报 404 时,报错信息里出现的是你装的那个包,而真正找不到文件的是另一个包(这次是 musl 本身)。它是”顺带”要装的东西,报错只报了个寂寞。光看报错很容易以为是源里没有 musl-dev,其实源里有,只是版本对不上。