升级到 Ubuntu 24.04(Noble)之后,我做的第一件事就是给测试机配上 Docker 官方仓库。按官网文档复制那几行命令,结果sudo apt update还没跑完,屏幕上就铺满了 GPG 密钥错误。这种报错在 Ubuntu 20.04、22.04 上并不常见,但到了 Noble 这一代,系统把仓库签名密钥的信任机制改成了更严格的模型,很多从旧版本升上来、或者习惯照抄旧教程的人都会撞进同一个坑:要么是一串NO_PUBKEY加十六进制编号,要么是gpg: no valid OpenPGP data found,还有一堆关于legacy trusted.gpg keyring的警告。这篇内容就把 Ubuntu 24.04 添加 Docker 官方仓库时遇到的 GPG 密钥缺失错误一次讲透:先说它为什么会出现,再给出一套完全可复现的正确流程,最后把高频报错逐条拆解,方便你对照排查。
1. 先弄清报错本质:apt 凭什么卡在 GPG 密钥上
1.1 一个典型报错现场
我先贴一段模拟的报错输出,关键指纹做了脱敏处理:
$ sudo apt update Hit:1 http://archive.ubuntu.com/ubuntu noble InRelease Hit:2 http://security.ubuntu.com/ubuntu noble-security InRelease Err:3 https://download.docker.com/linux/ubuntu noble InRelease The following signatures couldn't be verified because the public key is not available: NO_PUBKEY 1A2B3C4D5E6F7A8B W: GPG error: https://download.docker.com/linux/ubuntu noble InRelease: The following signatures couldn't be verified because the public key is not available: NO_PUBKEY 1A2B3C4D5E6F7A8B E: The repository 'https://download.docker.com/linux/ubuntu noble InRelease' is not signed. E: apt cannot update the repository. Please check its configuration.这段信息量其实很大。NO_PUBKEY表示 apt 在本地信任的密钥环境里找不到能匹配仓库签名所用的公钥;The repository is not signed是 apt 面对无法验证签名的仓库时的保守策略:宁可不更新,也不让未经身份验证的内容进入。最要命的是整个软件源更新会随之中断,其他仓库可能也会被一起拖住。如果你看到的是Err:3 ... public key is not available,问题几乎可以肯定出在密钥导入环节,而不是仓库地址写错。
1.2 apt 为什么坚持验证 GPG 签名
理解这个报错,得先清楚 apt 的签名验证机制。apt 更新软件源时,网络上拿到的是 Packages 索引文件。你当然不希望别人修改这个索引,让 apt 把恶意软件包装成正常包推给你。所以官方仓库会对索引的元数据文件做签名,签名用私钥生成,apt 用已经信任的公钥去验证。这就像收快递时快递员出示工作证,你验过证件才收件;没有对应的工作证,也就是没有公钥,apt 自然拒绝收件。
具体流程上,apt 会下载仓库的InRelease文件,里面带有 OpenPGP 签名,验证通过后才采用其中记录的哈希值去校验 Packages 索引。如果签名无法确认,整个仓库更新就处于不可信状态,apt 会直接报错并中断执行。所以 GPG 密钥缺失并不是 apt 的小脾气,而是它保护系统安全的基本手段。
1.3 Ubuntu 24.04 的信任模型变化
为什么这个错误在 Ubuntu 24.04 上格外多?因为 24.04 的仓库管理方式变了。旧版 Ubuntu 把第三方源的密钥放进全局信任库/etc/apt/trusted.gpg和/etc/apt/trusted.gpg.d/,用apt-key add导入,系统里所有软件源都会无条件信任这些密钥。但这种方式在安全上有个隐患:一旦某个第三方仓库密钥被攻破,攻击者可以拿它签名任何包,欺骗这台机器上的任意仓库。所以从 22.04 开始,apt-key就被标记为弃用,24.04 更是彻底走向了 deb822 格式的.sources文件,默认的/etc/apt/sources.list.d/ubuntu.sources就是一个典型例子。
新模型要求每个软件源通过Signed-By=明确指定自己信任的 keyring 文件,不再搞全局信任。这带来三个直接影响:
- 旧教程里
curl ... | sudo apt-key add -的写法在 24.04 上已经走不通,apt-key命令通常不可用; - 密钥要放到独立 keyring 文件里,常见位置是
/etc/apt/keyrings/或/usr/share/keyrings/; - 源条目里必须写
signed-by或Signed-By指向这个文件,apt 才会使用它。
正是这三处变化,让不少照着旧文章操作的人一头撞上 GPG 密钥错误。这也是为什么网上关于这个问题的求助贴突然变多——不是大家操作变差了,而是系统规则变了。
2. 一次正确添加 Docker 官方仓库的完整操作
2.1 先确认系统状态和依赖
开工之前,先确认系统版本,确保代号确实是 noble:
cat /etc/os-release输出里能看到VERSION_CODENAME=noble就可以了。如果你系统还停在 jammy,那不是 Ubuntu 24.04,错误模型也会略有不同,先把系统升上来再继续。
接着安装必要工具:
sudo apt update sudo apt install -y ca-certificates curl gnupg这三个包各司其职:ca-certificates提供 HTTPS 证书链,curl负责把 GPG 公钥文件从 Docker 官方仓库拉下来,gnupg则用来处理 OpenPGP 公钥的格式转换。缺了gnupg,后面执行gpg --dearmor会直接报 command not found;缺了curl,下载那一步就没法做。顺手把 ca-certificates 装上,也能避免后续出现证书类报错。
2.2 下载 GPG 密钥并转成二进制 keyring
这一步是避免NO_PUBKEY的关键。推荐命令如下:
sudo install -m 0755 -d /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg sudo chmod a+r /etc/apt/keyrings/docker.gpg几个点容易忽略。install -m 0755 -d确保目录存在且权限正确,避免后面写入时遇到问题。下载下来的原始内容是 ASCII armored 格式,开头是-----BEGIN PGP PUBLIC KEY BLOCK-----,而 apt 官方推荐把它转成二进制 keyring 文件,gpg --dearmor正是干这个的。最后chmod a+r很重要,apt 进程读取 keyring 文件时如果权限是600而只有某个用户可读,就会出现权限类报错,报错信息里通常带着permission denied字样。
步骤执行完,可以用下面这条命令验证文件已经是合法的 keyring:
sudo gpg --show-keys /etc/apt/keyrings/docker.gpg输出会列出公钥算法、创建时间和完整指纹。指纹的内容建议与 Docker 官方文档公布的一致,确认你拿到的不是某个奇怪来源替换过的文件。这一步虽然多花十秒,但能省下后面大量排查时间。
2.3 在独立源文件中挂载 docker 仓库
不要把 Docker 源直接塞进/etc/apt/sources.list,而是放到独立的/etc/apt/sources.list.d/docker.list文件里。这样做有两个理由:一是便于排查和移除,二是单个仓库故障不会污染主 sources.list。24.04 也推荐这种模块化管理方式。
传统单行格式:
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu noble stable" | sudo tee /etc/apt/sources.list.d/docker.list其中arch=$(dpkg --print-architecture)自动展开为 amd64 或 arm64,signed-by指向刚才生成的 keyring 文件,noble是发行版代号,stable是 Docker 仓库的组件名。
24.04 也完全支持 deb822 格式的.sources文件,可读性更好:
sudo tee /etc/apt/sources.list.d/docker.sources > /dev/null <<'EOF' Types: deb URIs: https://download.docker.com/linux/ubuntu Suites: noble Components: stable Signed-By: /etc/apt/keyrings/docker.gpg EOF两种写法效果等价。如果你系统的其他源已经在用.sources格式,那我建议统一用 deb822 风格,看起来更清晰,也更能融入 Ubuntu 24.04 的风格。
2.4 更新并验证安装结果
执行:
sudo apt update正常情况会看到 docker 仓库的InRelease被 Get 下来,不再有签名错误。随后检查包是否可见:
apt-cache policy docker-ce输出里如果能出现候选版本号,说明仓库添加成功,之后执行sudo apt install docker-ce docker-ce-cli containerd.io就能顺利装上。如果apt-cache policy里什么都查不到,大概率是源条目没有被 apt 读取,或者仓库地址本身有问题,继续看后面的章节。
3. 高频 GPG 错误逐个拆解与修复
3.1 NO_PUBKEY:密钥根本没有落进系统
这个报错最常见。除了完全没有执行密钥导入步骤外,还有几个隐藏原因:keyring 文件路径写错,源条目里的signed-by指向了不存在的文件;密钥文件虽然存在,但内容不是真正的公钥,而是一个 HTML 错误页或空文件;系统时间偏差太大,导致 apt 在验证签名时产生了异常表现。
排查步骤很简单:
ls -l /etc/apt/keyrings/docker.gpg sudo gpg --show-keys /etc/apt/keyrings/docker.gpg如果ls显示文件不存在,就去检查是不是当初写错了目录名,比如写成了/etc/apt/keys而不是/etc/apt/keyrings。如果gpg --show-keys报 "No public key" 或类似提示,说明文件损坏,直接重新生成即可。
修复手段:
sudo rm -f /etc/apt/keyrings/docker.gpg curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg sudo chmod a+r /etc/apt/keyrings/docker.gpg sudo apt update这里提醒一句:不要只删 keyring 文件而忘了修正源文件里的signed-by路径。路径写错是NO_PUBKEY的温床。
3.2 gpg: no valid OpenPGP data found:下载到的内容是错的
这个错误出现在gpg --dearmor一步,意思是 gpg 收到的东西根本不是 ASCII armored 公钥。最常见的情况是 curl 成功返回了 HTTP 200,但内容是 HTML 页面,比如网关、防火墙的拦截页,或者 CDN 临时返回的错误页。curl -fsSL的-f只对 HTTP 错误码生效,不会拦截内容本身,所以 gpg 拿到 HTML 后照样会抱怨。
怎么确认?执行:
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | head -n 5正常应该看到:
-----BEGIN PGP PUBLIC KEY BLOCK-----如果看到<!DOCTYPE html>或一段 JSON,那就是网络出口有问题。这个场景多发生在某些办公网络、公共 WiFi 或网络服务商对 download.docker.com 做了特殊处理的环境里。处理办法:
- 换个能正常访问该域名的网络出口,手机热点通常最快;
- 稍等片刻重试,CDN 抖动过一会儿会恢复;
- 找一台能正常下载 gpg 文件的机器,把文件合法传输回本机再使用。
这类问题排查到最后会发现根本不是 apt 的配置错误,而是拿不到正确的源头文件。别在那条命令上反复重试,先看内容。
3.3 Key is stored in legacy trusted.gpg keyring 警告
很多从旧版本升级到 24.04 的系统,以前用apt-key add导入过 Docker 密钥,升级后每次apt update都会刷出一条很长的警告,内容大致是:
Key is stored in legacy trusted.gpg keyring (/etc/apt/trusted.gpg), see the DEPRECATION section in apt-key(8) for details.这个警告不直接影响安装,但说明系统处于一种不规范且存在风险的信任状态:密钥被全局信任,任何使用该密钥签名的包都能通过验证。修复的方向是把密钥搬到独立 keyring,并在 Docker 源条目里写signed-by。
实际操作时要注意,24.04 上apt-key del这种命令已经不可靠了。正确方向是:先把 Docker 源条目改成 2.3 里带signed-by的写法,再把原来全局信任的密钥文件移除。旧系统里密钥可能出现在/etc/apt/trusted.gpg.d/目录下,也可能直接写在/etc/apt/trusted.gpg这个二进制文件里。操作前先备份:
sudo cp -a /etc/apt/trusted.gpg /etc/apt/trusted.gpg.bak 2>/dev/null || true sudo cp -a /etc/apt/trusted.gpg.d /etc/apt/trusted.gpg.d.bak 2>/dev/null || true备份后找出属于 docker 的密钥文件并删除,再按 2.2 和 2.3 重新导入和配置,警告就会消失。如果你确认这台机器上 Docker 密钥只存在于/etc/apt/trusted.gpg.d/docker.gpg,直接删除那个文件是最快的。
3.4 The repository ... does not have a Release file
这个报错虽然不直接带 GPG 字样,但经常和 GPG 问题混在一起出现。它通常意味着仓库 URL 指向的 dists 目录里,没有你要找的发行版代号。比如系统明明是 noble,但sources.list.d里写的是:
deb [arch=amd64 signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu focal stableDocker 仓库明明支持 noble,你却被旧教程带着写了 focal,apt 去访问不存在的目录,自然找不到 Release 文件,于是判定仓库无效。
排查方式很直接:
curl -fsSL -o /dev/null -w "%{http_code}\n" https://download.docker.com/linux/ubuntu/dists/noble/Release返回200说明 noble 在 Docker 仓库里存在;返回404说明代号写错。还有一个隐蔽场景:.sources文件里Suites: noble stable这种写法会把stable误当成套件名,语法上不成立。按 2.3 的模板写最省心,不要自己加料。
3.5 证书类错误:unknown authority 和证书失效
报错里如果出现Certificate is signed by an unknown authority,通常不是 GPG 密钥问题,而是系统信任链或时间问题。我排查这类报错的固定顺序:
date sudo timedatectl set-ntp true systemctl restart systemd-timesyncd sudo apt install --reinstall ca-certificates如果系统时间比真实时间差了好几天,OpenPGP 签名验证会受公钥有效期影响而失败,apt 对 HTTPS 证书的校验也会一起异常。先校正时间再看签名错误是否消失。对于已经装有 ca-certificates 的机器,强制重装一遍还能把可能损坏的证书链文件补回来。
4. 不要盲猜,用一套流程把问题定位出来
4.1 快速核对 keyring 与仓库条目
遇到任何 GPG 错误,我都建议先做一次信息收集。最常看的三个位置是 keyring 文件、Docker 源文件和 apt 日志:
sudo gpg --show-keys /etc/apt/keyrings/docker.gpg cat /etc/apt/sources.list.d/docker.list sudo tail -n 30 /var/log/apt/term.loggpg --show-keys确认密钥文件可解析;cat确认signed-by路径与实际一致;日志里能看到 apt 到底在哪一步失败。有时候错误已经写在日志里,只是apt update的终端输出把它吞掉了一部分。日志文件记录的信息比终端更完整,值得养成先看日志的习惯。
4.2 用 apt-cache 和 curl 交叉验证
如果仓库配置看着没问题,但apt update依旧报错,用 curl 直接访问源地址能快速区分是本地还是远端问题:
curl -fsSL https://download.docker.com/linux/ubuntu/dists/noble/Release | head -n 5 curl -fsSL https://download.docker.com/linux/ubuntu/gpg | head -n 3第一个应该输出 Release 文件头部,第二个应该输出BEGIN PGP PUBLIC KEY BLOCK。任何一个不像预期,问题大概率在网络层或仓库地址本身。如果仓库访问正常,再清理一次 apt 的缓存状态:
sudo rm -rf /var/lib/apt/lists/*docker* 2>/dev/null sudo apt clean sudo apt update清理 lists 目录有时能解决因缓存了错误签名元数据而导致的顽固报错。这类问题不是每次都出现,遇到一次就能体会到先清缓存的好处。
4.3 常见 GPG 错误速查表
| 报错关键字 | 直接原因 | 最快定位 | 修复方向 |
|---|---|---|---|
NO_PUBKEY xxxx | 本地没有对应公钥 | gpg --show-keys /etc/apt/keyrings/docker.gpg | 重建并导入 keyring |
no valid OpenPGP data | 下载到 HTML 或空文件 | curl .../gpg | head -n 5 | 换网络出口重试 |
legacy trusted.gpg keyring | 密钥放进了全局信任区 | 检查/etc/apt/trusted.gpg* | 迁移到独立 keyring |
does not have a Release file | 代号或组件名错误 | curl -I .../dists/noble/Release | 修正源条目中的代号 |
unknown authority | CA 缺失或时间不准 | date+ 重装 ca-certificates | 校准时间并重装 CA 包 |
permission denied | keyring 文件不可读 | ls -l .../docker.gpg | chmod a+r修复 |
表格里是浓缩后的常见场景。实际排查时不要拘泥于单个报错文本,要把源条目、keyring 文件、网络环境、系统时间放在一起看。绝大多数情况下,一件事做好了——密钥正确导入到独立 keyring 并被signed-by引用——GPG 类错误会消失大半。
5. 关于密钥管理,我踩过几次坑之后的一些习惯
5.1 每个仓库一个专用 keyring,不要全局信任
我在很长一段时间里都习惯把所有第三方 GPG 密钥一股脑导进/etc/apt/trusted.gpg.d/,因为早年教程都这么写。后来改成独立 keyring 后,才真正意识到全局信任的风险边界有多大:一个被攻陷或误用的密钥,可以给这台机器上所有仓库的包开绿灯;而独立 keyring 配合signed-by,只是把这个仓库的信任范围圈在一个文件里。哪怕要维护 Docker 和其他多个第三方源,我也坚持每个源一个.gpg文件。后续发现某个第三方仓库不再维护,移除时也很干净:删掉 keyring 和源文件,什么都不剩。
5.2 升级系统后先确认上游仓库支持新代号
Ubuntu 发布新版本后,第三方仓库不一定立刻跟进。Ubuntu 24.04 发布时,多数主流软件都已经补上 noble 目录,但总有个别仓库会慢几周。如果你在apt update时看到仓库不可用的报错,先别急着怀疑自己的操作,拿 curl 去访问一下dists/noble/Release,返回 404 就是上游还没跟上来。这种情况只能等上游更新,或者暂时用旧版本源过渡,但不要在新系统里长期混用旧代号源,依赖解析迟早出问题。
5.3 把配置固化成脚本,避免重复踩坑
我现在装新机器的时候,会把密钥导入、源文件写入整理成一个简短脚本,每次直接运行:
sudo install -m 0755 -d /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg sudo chmod a+r /etc/apt/keyrings/docker.gpg echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu noble stable" | sudo tee /etc/apt/sources.list.d/docker.list sudo apt update脚本里刻意不用旧的apt-key写法,也不把源塞进主sources.list,这样即使半年后再跑也没问题。有个小技巧可以让脚本在不同版本间通用:直接取当前系统的代号,而不是写死 noble。
CODENAME=$(. /etc/os-release && echo "$VERSION_CODENAME") echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $CODENAME stable" | sudo tee /etc/apt/sources.list.d/docker.list只要上游仓库已经支持当前系统,这个脚本就能自动适配版本,省去每次手改代号的麻烦。这也是我在反复遇到 GPG 错误后,最终沉淀下来的习惯:与其每次修,不如把正确流程固定下来。