第二十三章. 私有制品库构建:GitHub Packages 与 GHCR 全流程实战
第二十三章. 私有制品库构建:GitHub Packages 与 GHCR 全流程实战
Prorise第二十三章. 私有制品库构建:GitHub Packages 与 GHCR 全流程实战
摘要:本章将带领你搭建企业级的私有制品供应链。我们将摒弃枯燥的理论,直接动手构建一个 Spring Boot 后端服务和一个 Vite 前端组件库。在实战过程中,我们将一步步打通 GitHub Packages 的 鉴权体系,配置自动化发布流水线,并亲手解决“跨项目权限”这一工程难题。
本章学习路径
- 资产准备:在 GitHub 后台申请“万能钥匙”(PAT),为本地发布做好铺垫。
- Docker 供应链:从零创建一个 Spring Boot 项目,编写 Dockerfile 并注入元数据,建立 GitHub Actions 自动化构建流。
- NPM 供应链:初始化一个 Vite TypeScript 项目,改造
package.json适配私有源,实现组件库的云端发布。
23.1. 鉴权体系基石:握有“门禁卡”
在开始写任何代码之前,我们需要先解决一个根本问题:GitHub Packages 是私有的,你的本地终端(Terminal)和你的 CI 机器人(Actions)如何证明“我是我”?
GitHub 的认证体系在本地和云端是完全隔离的,这是新手最容易混淆的地方。
23.1.1. 本地开发专用:配置 PAT (Personal Access Token)
在 2021 年之后,GitHub 已经禁止使用账号密码在命令行登录。为了在本地电脑执行 docker push 或 npm publish,我们需要一把“物理钥匙”。
操作步骤:
- 登录 GitHub,点击头像 -> Settings -> 左侧最底部 Developer settings。
- 选择 Personal access tokens -> Tokens (classic) -> Generate new token (classic)。
- 权限勾选(关键一步):
write:packages:允许发布和上传包(勾选后会自动选中read:packages)。delete:packages:允许删除包的版本(强烈建议勾选,便于后续清理测试数据)。repo:如果你的仓库是 Private(私有)的,必须勾选此项,否则无法读取源码。
保存凭证:生成的 Token(以 ghp_ 开头)只显示一次。请立刻复制它。
为了安全起见,我们不要每次都手动粘贴 Token,也不要将其写入代码。我们将其配置为系统环境变量。
终端配置(Mac/Linux/Git Bash):
1 | # 将 Token 写入环境变量(替换为你的真实 Token) |
23.1.2. CI 环境专用:GITHUB_TOKEN
你的本地电脑有了 CR_PAT,那 GitHub Actions 的服务器怎么办?
不需要任何操作。GitHub 会为每一次 Workflow 运行动态生成一个一次性的 ${{ secrets.GITHUB_TOKEN }}。它比 PAT 更安全,因为它在运行结束后自动失效。我们将在稍后的 Pipeline 配置中直接使用它。
23.2. GHCR 实战:Spring Boot 镜像的私有化交付
现在我们有了钥匙,接下来构建第一个“货物”。我们将创建一个标准的 Spring Boot 项目,并将其打包推送到 GitHub Container Registry (GHCR)。
23.2.1. 初始化 Spring Boot 项目
首先,我们需要一个真实的后端工程。不要凭空想象,请跟随以下步骤操作:
步骤 1:创建项目
使用 curl 快速从 Spring Initializr 下载一个包含 Web 依赖的 Demo 项目,或者你也可以手动在 IDEA 中创建。
1 | # 1. 下载脚手架 (Java 17, Maven, Web 依赖) |
步骤 2:创建 Dockerfile
在项目根目录下新建 Dockerfile。在这里,我们需要引入 GHCR 的核心规范。
文件路径:Dockerfile
1 | # 使用轻量级 JDK 基础镜像 |
为什么要有 LABEL?
如果你不写这行 LABEL,推送到 GHCR 的镜像将不会显示在你的仓库首页右侧,而是作为一个“无主”的 Package 散落在你的个人主页里,难以管理。
23.2.2. 编写自动化构建流水线
现在,我们要教 GitHub Actions 如何使用 GITHUB_TOKEN 登录 GHCR 并推送镜像。
步骤 1:创建 Workflow 文件
在项目根目录创建 .github/workflows/deploy-image.yml:
1 | name: Build and Push Docker Image |
步骤 2:提交并触发
将代码推送到 GitHub(请确保你已经在 GitHub 创建了对应的空仓库并关联了 remote)。
1 | git add . |
前往 GitHub 仓库的 Actions 页面,等待构建成功。成功后,回到仓库首页(Code tab),你会惊喜地发现右侧边栏出现了一个 Packages 区域,里面列出了 backend-service 镜像。
23.2.3. 本地验证:拉取私有镜像
现在镜像在云端了,我们在本地电脑上尝试拉取它。
错误示范:直接运行 docker pull ghcr.io/prorise-cool/testgithubpackage:main。你会收到报错:denied: denied,如果您遵循 23.1 节配置的环境变量,那么这则指令会自动成功
23.3. NPM 实战:Vite 组件库的私有化发布
搞定了后端镜像,现在我们来挑战前端。场景是:你需要开发一个公司内部通用的 UI 组件库,发布到 GitHub Packages 供其他前端项目安装。
23.3.1. 初始化 Vite 项目与 Scope 规范
GitHub Packages 对 NPM 包有一个强制性的 “Scope(作用域)”规范。你不能发布一个叫 my-utils 的包,必须发布 @你的用户名/my-utils。
步骤 1:创建项目
使用 Vite 快速初始化一个 TypeScript 库项目。
1 | # 1. 创建项目 |
步骤 2:改造 package.json
打开 package.json,这是最关键的一步。我们需要告诉 NPM:“不要往 npmjs.com 推送,而是往 GitHub 推送”。
1 | { |
23.3.2. 配置 .npmrc:路由与鉴权的分离
配置了 package.json 只是告诉了 NPM “要去哪”,但没告诉它“怎么进门”。我们需要 .npmrc 文件。
场景一:项目级配置(路由映射)
在项目根目录创建 .npmrc 文件。这个文件会提交到 Git,所以 绝对不能包含 Token。它的作用是告诉安装依赖的人:“凡是 @YourUsername 开头的包,都去 GitHub 找”。
文件路径:.npmrc
1 | # 告诉 NPM 客户端,@YourUsername 作用域下的包,源地址是 GitHub |
场景二:用户级配置(本地鉴权)
在你的电脑主目录(~/.npmrc)下,配置刚才申请的 PAT。
1 | # 在终端执行(追加配置到用户配置文件) |
23.3.3. 配置自动化发版 Actions
前端项目的发版通常不需要每次 Push 都触发,最符合语义化的工作流是:当你决定发布一个 Release 版本时,CI 自动将其推送到私有仓库。
由于现在的项目大多转向了更高效的 pnpm,这里提供了两种主流包管理器的配置方案。
步骤 1:创建 Workflow 文件
新建 .github/workflows/npm-publish.yml,根据你的项目类型选择对应的配置:
如果你的项目使用原生 npm,使用此配置。
1 | name: Publish NPM Package |
如果你的项目使用 pnpm,需要额外安装 pnpm 环境,并处理特殊的发布参数。
1 | name: Publish PNPM Package |
步骤 2:执行发布全流程
- 本地准备:修改
package.json中的version(例如从1.0.0改为1.0.1),并提交代码推送到main分支。 - 创建 Release:
- 在 GitHub 仓库首页点击右侧 Releases。
- 点击 Draft a new release。
- Choose a tag:输入
v1.0.1(建议与 package.json 版本保持一致)。 - 点击 Publish release。
- 验证结果:
- 跳转到 Actions 标签页,观察 Workflow 运行状态。
- 成功后,回到仓库首页,右侧 Packages 列表应显示最新的版本。
23.3. 本节小结
- 双重身份:本地开发必须配置
~/.npmrc(PAT),CI 环境利用setup-node配合GITHUB_TOKEN自动生成凭证。 - Scope 铁律:
package.json的 name 必须包含@Scope,且必须与publishConfig配合使用。 - 安全红线:项目根目录的
.npmrc只做路由映射,严禁包含任何_authToken信息。
23.4. 消费闭环:在业务项目中安装私有依赖
在上一节,我们成功将 @YourUsername/ui-library 发布到了云端。但对于大多数开发者来说,“发包”只是完成了一半,真正的噩梦往往始于“装包”。
这是一个典型的场景:你创建了一个新的业务项目(消费者),兴奋地在 package.json 里添加了依赖,结果执行 npm install 时,终端直接甩给你一个 404 Not Found 或者 401 Unauthorized。
本节我们将解决这个工程化难题:如何在 B 项目中,安全地安装 A 项目发布的私有包?
23.4.1. 消费者视角的 .npmrc 配置
首先,我们需要告诉消费者项目(Consumer Project)两件事:
- 路由:遇到
@YourUsername开头的包,别去 npm 官方源找,去 GitHub 找。 - 鉴权:我有合法的身份令牌。
步骤 1:创建项目级 .npmrc
在你的业务项目(例如 my-business-app)根目录下创建 .npmrc 文件。
文件路径:my-business-app/.npmrc
1 | # 1. 路由映射:明确告诉 npm 客户端,这个 Scope 归 GitHub 管 |
步骤 2:本地安装测试
在本地开发时,由于你在 23.1 节已经在用户主目录(~/.npmrc)配置了全局 Token,且配置了 NODE_AUTH_TOKEN 环境变量(或 npm 客户端会自动回退读取 User Config),此时直接安装应该能成功。
1 | # 本地安装私有依赖 |
如果安装成功,说明路由配置正确。
23.4.2. CI/CD 中的跨仓库安装(Cross-Repo Access)
真正棘手的问题出现在 CI 流水线上。
当 my-business-app 的 GitHub Actions 运行时,它默认使用的 GITHUB_TOKEN 仅拥有当前仓库的权限。它没有权限去读取你另一个仓库(如 ui-library)里的 Packages。
这就是著名的“跨仓库访问(Cross-Repository Access)”死结。
解决方案:使用 PAT 破局
我们需要将在 23.1 节申请的那个“万能钥匙”(PAT),作为 Secret 注入到业务项目中。
存储 Secret:
- 进入
my-business-app仓库的 Settings -> Secrets and variables -> Actions。 - 点击 New repository secret。
- Name:
PACKAGES_READ_TOKEN(名字清晰即可)。 - Value: 粘贴你的 PAT(必须包含
read:packages权限)。
- 进入
配置 Workflow:修改业务项目的构建流程,将这个 Secret 注入给包管理器。这里同样提供 NPM 和 PNPM 两种配置:
1 | name: Build Business App (NPM) |
1 | name: Build Business App (PNPM) |
原理总结:我们绕过了默认的 GITHUB_TOKEN,而是强制 npm/pnpm 客户端使用我们拥有更高权限的 PAT。这样,无论私有包在哪个仓库,只要该 PAT 有读取权,CI 就能顺利拉取。
23.5. 治理与维护:防止存储爆炸
GitHub Packages 的免费额度通常只有 500MB(Free 账号)或 2GB(Pro 账号)。对于 Docker 镜像来说,这简直是杯水车薪。一个未优化的 Spring Boot 镜像可能就有 200MB,几次构建就能把空间撑爆。
一旦超额,你的 Actions 就会报错,甚至导致无法推送新代码。我们需要引入“自动垃圾回收机制”。
23.5.1. 存储空间预警
你可以在 Settings -> Billing and plans 中查看当前的存储用量。如果发现红色预警,第一反应通常是去 Packages 页面手动删除。
但手动删除非常痛苦:你需要点进每一个 Version,点击 Settings,输入确认码,点击删除…重复 50 次。
23.5.2. 自动化清理策略
我们将使用官方推荐的 actions/delete-package-versions 来实现自动化治理。
策略设计:
- 保留:最近发布的 3 个正式版本(Release)。
- 删除:所有的开发版镜像(Snapshot/dev),或者超过 30 天的旧镜像。
- 触发:每天凌晨自动执行,或在推送代码后触发。
实战配置:在 backend-service(Docker 项目)中添加清理流程。
文件路径:.github/workflows/cleanup-packages.yml
1 | name: Cleanup Old Images |
关键参数解释:
delete-only-untagged-versions: Docker 构建过程中会产生很多<none>标签的中间层镜像,这些是占用空间的元凶。将此项设为true是最安全的瘦身方式。ignore-versions: 使用正则保护你的生产环境 Tag,防止误删。
23.6. 本章总结
私有制品库是企业级工程化的分水岭。通过本章的学习,你不仅学会了如何发布包,更重要的是掌握了这一整套复杂的鉴权逻辑。
23.6.1. 核心知识体系
| 场景 | 关键痛点 | 解决方案 | 核心配置/命令 |
|---|---|---|---|
| 本地发布/拉取 | 密码登录已废弃 | 使用 Classic PAT | docker login -u user -p PAT |
| CI 发布 (Producer) | 默认权限不足 | 使用 GITHUB_TOKEN | permissions: packages: write |
| CI 安装 (Consumer) | 跨仓库无权限 | 使用 PAT (Secret) | .npmrc + NODE_AUTH_TOKEN |
| 制品关联 | 镜像无法显示在仓库页 | 缺少 OCI 元数据 | LABEL org.opencontainers.image.source |
| 存储治理 | 免费额度耗尽 | 自动化清理 | actions/delete-package-versions |
23.6.2. 常见问题速查 (FAQ)
Q: 为什么我推送到 GHCR 的镜像在 Packages 页面能看到,但仓库主页右侧没有?
- A: 99% 是因为 Dockerfile 里漏写了
LABEL org.opencontainers.image.source=...。补上后重新构建推送即可。
- A: 99% 是因为 Dockerfile 里漏写了
Q: 消费端项目报错
401 Unauthorized,但我已经在.npmrc里配置 Token 了?- A: 检查
.npmrc中的 Token 引用方式。如果写的是${NODE_AUTH_TOKEN},请确保你的环境变量里真的有这个值。在本地,你需要检查~/.npmrc是否有对应 Scope 的 Auth 配置。
- A: 检查
Q: 可以将私有包变成公开包吗?
- A: 可以。进入 Package 的 Settings 页面,滑动到底部 Danger Zone,点击 “Change visibility”。注意,一旦公开,任何人都可以下载你的代码。
恭喜你!现在你已经拥有了一套完整的软件交付供应链。你的代码不再只是一堆文本,而是可以被自动打包、分发、版本化管理的工业级制品。接下来,我们将进入 GitHub CLI 的世界,看看如何摆脱浏览器,在终端里掌控一切。

















