大家好,我是程序员柏风,AI 最全的 Vibecoding 学习地图 http://coderoadmap.cn 作者。
构建推送镜像
这一课你将能做到
- 写出使用
docker/login-action+docker/build-push-action的 job,把镜像推到 GHCR(ghcr.io) - 配置推包所需的
permissions: packages: write,并用github.sha/latest说明 tag 策略的取舍 - 结合仓库已有的
backend/Dockerfile、platform/Dockerfile,说明 CI 镜像构建与本地docker build的关系
为什么要学
测绿了还不够:K8s / Compose / 单机 Docker 跑的是镜像,不是 runner 上的临时目录。CI 负责「从 Dockerfile 构建并推到镜像仓库」,部署侧才有稳定可拉的引用。
GitHub 生态里 GHCR 与仓库同账号:常用 GITHUB_TOKEN 登录,少维护一套 Docker Hub 密码(仍要管好权限与可见性)。
本课聚焦 build-push;「谁把新 tag 写进集群」交给下一课部署策略。
先建立图像
CI(已测过,或同一流水线里 needs: [测试] 的后续 job)
checkout
docker login ghcr.io(用 GITHUB_TOKEN)
docker build(context: backend 或 platform)
docker push
└─ ghcr.io/<owner>/coderoadmap-backend:<sha>
└─ ghcr.io/<owner>/coderoadmap-backend:latest (可选)
之后
Helm values 的 image.tag = <sha>,或 Compose image: …:<sha>
人话:镜像是打包好的「可运行盒饭」;GHCR 是食堂冰箱。CI 负责炒好推进冰箱,部署侧负责写「今天吃哪一盒」的标签。
| Tag 策略 | 用途 | 注意 |
|---|---|---|
完整 github.sha | 每次 commit 可追溯 | 名字长,回滚最精确 |
| 短 sha(7 位) | 好读 | 仍建议保留完整 sha 可查 |
semver 1.2.3 | 正式发布 | 需要发布流程配合 |
latest | 方便试玩 | 可变,生产慎用 |
跟着做
步骤 1:本地先验证 Dockerfile
# 在仓库根,按项目文档构建
docker build -t coderoadmap-backend:local ./backend
# 若有 platform Dockerfile:
docker build -t coderoadmap-platform:local ./platform
本地构建失败,不要指望 Actions 变绿。记下 build context 目录与是否需要 build-args。
步骤 2:写 backend 推送 job
name: Docker
on:
push:
branches: [main]
workflow_dispatch:
permissions:
contents: read
packages: write # 推 GHCR 必需
jobs:
docker-backend:
runs-on: ubuntu-latest
steps:
- name: 拉取代码
uses: actions/checkout@v4
- name: 登录 GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
# 内置 token;配合 packages: write
password: ${{ secrets.GITHUB_TOKEN }}
- name: 构建并推送
uses: docker/build-push-action@v6
with:
context: backend
push: true
tags: |
ghcr.io/${{ github.repository_owner }}/coderoadmap-backend:${{ github.sha }}
ghcr.io/${{ github.repository_owner }}/coderoadmap-backend:latest
推 main 或手动运行后:到 GitHub Packages 页确认镜像出现;首次推送后记得检查包可见性设置。
步骤 3:为 platform 复制对称 job
把 context: platform、镜像名改为 coderoadmap-platform,可作第二个并行 job,也可用 matrix 复用步骤。仓库若已有 docker.yml,对照本课骨架读差异,而不是盲目覆盖。
步骤 4:核对权限与可见性
- workflow 缺
packages: write时,push 常 403。 - Package 可见性 private/public 按项目策略设置;改了可见性再验证拉取权限。
- 组织仓库还可能受组织级「Actions 写 packages」策略约束。
步骤 5:定 tag 纪律
笔记写三条本站约定(可按团队调整):
- 部署引用 sha tag,可回滚到精确 commit
latest仅开发便利,生产 values 不盯 latest- 正式发版另打 semver(可与 GitHub Release 联动,进阶)
步骤 6:与 CI 的衔接方式
两种常见:
- 同 workflow:build-push job 写
needs: [backend, platform]——测试红就不推镜像 - 分 workflow:
ci.yml管测试,docker.yml仅在mainpush(已受保护)时推
入门推荐:测与推分离或强 needs,避免红测仍推镜像污染 registry。
讲清楚要点
- 因为运行环境吃镜像,所以 CD 链条通常以「可拉取的 image:tag」为界——不是把 runner 目录 scp 到生产(小站有意为之除外)。
- 因为 GHCR 与 GitHub 一体,所以常用
GITHUB_TOKEN+packages: write——少维护第二套密码,权限随 workflow 声明走。 - 因为 tag 决定回滚粒度,所以生产偏向不可变 sha,而不是漂浮的 latest——出问题能精确指回 commit。
- 因为 Dockerfile 定义构建上下文,所以
context必须指向含 Dockerfile 的正确目录——monorepo 里 backend/platform 各自独立。 - 因为推送是写操作,所以权限与包可见性要显式设计——尤其开源仓库要防滥用写权限。
- 因为镜像构建耗时,所以用 buildx 层缓存(
cache-from/cache-to)提速——先保证正确推送,再优化速度。
你可能会懵的地方
-
「
GITHUB_TOKEN从哪来?要放进 Secrets 吗?」 每个 job 自动注入,不必再存一份。要做的是在 workflow 顶层用permissions声明它需要的最小能力。 -
「镜像名里的 owner 大小写?」 GHCR 对路径大小写敏感。
github.repository_owner原样输出;若实际包路径是小写,用表达式函数统一(如lower()场景可改用 metadata-action 自动规整)。 -
「PR 里也 push 镜像吗?」 不要。fork PR 的 token 能力受限且 PR 数量多,容易产生垃圾 tag 甚至被滥用。常见做法:仅
mainpush 或 tag 发布时推。 -
「build-push-action 和手写 docker CLI?」 action 封装了 buildx、tags、多平台构建与缓存参数;手写
docker build && docker push也可,但多平台与层缓存要自己管。入门用官方 action。 -
「推了镜像就等于部署了吗?」 不是。镜像只是产物;集群/服务器要更新引用(image.tag)并拉取后,环境才变化。这是下一课的核心。
错法对照
| 错法 | 问题 | 正解 |
|---|---|---|
无 packages: write 就 push | 403 denied | 顶层 permissions 显式声明 |
生产 values 只跟 latest | 不可追溯、易被覆盖 | 钉 sha 或 semver |
| 测试失败仍推镜像 | 污染 registry、坏镜像可被拉取 | needs 挂检查 job 或分 workflow |
| context 指仓库根但 Dockerfile 在子目录 | 找不到 Dockerfile 或上下文错乱 | context: backend 并确认 Dockerfile 路径 |
| 每次全量构建不缓存 | 镜像层重复下载编译 | buildx cache-from/cache-to(type=gha) |
卡点急救
-
push 报
denied/ 403:检查 workflow 顶层是否有packages: write;组织仓库确认未被策略拦截;首次推包后到 Packages 页确认可见性与仓库关联(Connect repository)。 -
构建很慢:加 buildx 缓存——先
uses: docker/setup-buildx-action@v3,再给 build-push-action 加:cache-from: type=gha cache-to: type=gha,mode=max同时确认 Dockerfile 分层合理(依赖层在源码层之前,lock 未变就命中缓存)。
-
镜像推上去了但 tag 不对/少推:
tags:是多行列表,逐行核对表达式展开结果;不确定时先push: false跑一次看输出,或在 step 前用echo ${{ github.sha }}确认值。
面试常问
-
镜像 tag 策略怎么设计?为什么生产不建议用 latest? 生产引用不可变 tag(commit sha 或语义化版本),
latest只作开发便利。latest 是可变指针:今天指向 A 版,明天被覆盖成 B 版,回滚与审计都失去锚点;出问题时「集群里跑的到底是哪次提交」都答不上来。tag 纪律的本质是把「镜像」与「代码版本」一一对应。 -
Docker 层缓存原理是什么?CI 里怎么利用? Docker 按层缓存:指令与输入不变则命中。Dockerfile 应把「少变层在前、多变层在后」——先 COPY lock 装依赖,再 COPY 源码。CI 里用 buildx 的
cache-from/cache-to: type=gha把层缓存挂到 GitHub 缓存后端,跨 run 复用;lock 未变时依赖层直接命中,构建只剩源码编译段。 -
多阶段构建对 CI 有什么价值? 编译阶段(Maven/Node 全量工具链)与运行阶段(只有 JRE/standalone 运行时)分离,最终镜像更小、攻击面更小;CI 里 build-push 一次完成「编译+打包」,部署侧拉到的就是精简产物。结合本站:backend 镜像只含 JRE + jar,platform 镜像只含 node + standalone 产物。
自测三问
- 推 GHCR 通常需要哪个 permission?在 workflow 哪里声明?
- 为什么生产更推荐 sha tag 而不是 latest?
- monorepo 里 backend 与 platform 镜像的
context分别是什么?
小结一句话
build-push-action + GHCR 把 Dockerfile 变成可部署的镜像交付物;用 sha tag 保可追溯。
延伸
- Working with the Container registry
- docker/build-push-action
- 下一课:部署策略——镜像进了仓库之后,谁把它送到环境。