1. 为什么需要双镜像仓库推送?
在容器化部署的实践中,镜像仓库的选择往往成为团队需要权衡的问题。Docker Hub作为最老牌的公共镜像仓库,拥有最广泛的兼容性和社区支持,但免费账户存在拉取频率限制;而GitHub Container Registry(GHCR)作为GitHub原生服务,与代码仓库无缝集成,提供更精细的权限控制。实际场景中我们常遇到:
- 混合云环境需求:部分CI节点可能只能访问特定仓库
- 灾备考虑:避免单点故障导致部署中断
- 权限分离:开发阶段使用GHCR内部仓库,生产发布使用Docker Hub
- 网络优化:不同地理位置的集群可能访问不同仓库速度差异显著
去年我们的生产环境就曾因Docker Hub临时限流导致部署失败,后来通过GHCR的备用方案快速恢复了服务。这种双推送策略相当于为你的镜像上了"双保险"。
2. 基础环境准备
2.1 账户与Token配置
首先需要在两个平台创建访问凭证:
-
Docker Hub:
- 登录后进入Account Settings → Security
- 点击"New Access Token",建议权限选择"Read & Write"
- 记录生成的token(只会显示一次)
-
GHCR:
- 访问GitHub Settings → Developer settings → Personal access tokens
- 勾选
write:packages和read:packages权限 - 同样妥善保存生成的token
安全提示:这些token务必存储在GitHub Secrets中,绝对不要直接写在代码里。我见过多个项目因意外提交了明文token而导致仓库被恶意利用的案例。
2.2 项目结构规划
建议的代码仓库结构示例:
code复制.
├── .github
│ └── workflows
│ └── docker-build.yml # Actions配置文件
├── Dockerfile
├── build-args.txt # 构建参数文件(可选)
└── scripts
└── healthcheck.sh # 自定义健康检查脚本
这种结构将CI相关文件集中管理,保持项目根目录整洁。特别建议将Dockerfile的构建参数单独提取到build-args.txt中,方便不同环境配置。
3. GitHub Actions工作流详解
3.1 完整workflow配置
以下是经过生产验证的配置模板:
yaml复制name: Docker Image CI
on:
push:
branches: [ "main" ]
tags: [ "v*.*.*" ]
pull_request:
branches: [ "main" ]
env:
REGISTRY_DOCKER: docker.io
REGISTRY_GHCR: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
build-and-push:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Log in to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_TOKEN }}
registry: ${{ env.REGISTRY_DOCKER }}
- name: Log in to GHCR
uses: docker/login-action@v2
with:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
registry: ${{ env.REGISTRY_GHCR }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v4
with:
images: |
${{ env.REGISTRY_DOCKER }}/${{ env.IMAGE_NAME }}
${{ env.REGISTRY_GHCR }}/${{ env.IMAGE_NAME }}
- name: Build and push
uses: docker/build-push-action@v3
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
3.2 关键配置解析
-
触发条件:
- 主分支推送和版本标签触发完整构建
- PR时只构建不推送(安全考虑)
-
双登录机制:
- 使用
docker/login-action分别登录两个仓库 - GHCR可以直接使用
GITHUB_TOKEN,无需额外配置
- 使用
-
元数据自动生成:
metadata-action会自动处理镜像tag:latest(最新提交)sha-xxxxxx(对应commit)v1.0.0(当推送tag时)
-
并行推送:
- 构建一次镜像,同时推送到两个registry
- 通过
images:字段指定多个目标地址
4. 高级优化技巧
4.1 构建缓存优化
在build-push-action中添加缓存配置:
yaml复制- name: Build and push
uses: docker/build-push-action@v3
with:
cache-from: type=gha
cache-to: type=gha,mode=max
这利用GitHub Actions的缓存机制,可以显著加速重复构建。实测在Node.js项目中,第二次构建时间从3分钟缩短到40秒。
4.2 多架构支持
对于需要支持ARM等架构的场景,添加platforms参数:
yaml复制platforms: linux/amd64,linux/arm64
注意这需要先设置QEMU:
yaml复制- name: Set up QEMU
uses: docker/setup-qemu-action@v2
4.3 安全扫描集成
在推送前添加安全扫描步骤:
yaml复制- name: Scan for vulnerabilities
uses: docker/scout-action@v1
with:
image: ${{ env.REGISTRY_DOCKER }}/${{ env.IMAGE_NAME }}
token: ${{ secrets.DOCKER_HUB_TOKEN }}
5. 常见问题排查
5.1 认证失败问题
症状:收到"unauthorized: authentication required"错误
排查步骤:
- 检查token是否过期(Docker Hub token默认30天有效期)
- 确认secrets中的变量名与workflow中引用一致
- 对于GHCR,确保仓库的
packages权限已开启
5.2 推送超时问题
症状:推送过程中卡住最终超时
解决方案:
- 添加超时控制:
yaml复制timeout-minutes: 15 - 分步推送:先build保存到本地,再分别push到两个仓库
- 检查网络状况,某些地区可能需要配置代理
5.3 标签混乱问题
症状:错误的tag被推送到仓库
预防措施:
- 明确过滤tag格式:
yaml复制tags: | ${{ startsWith(github.ref, 'refs/tags/') && github.ref_name }} - 使用metadata-action的过滤功能
6. 生产环境最佳实践
经过多个项目的实战验证,总结出以下经验:
-
标签策略:
- 使用语义化版本(semver)作为主要tag
- 为最新稳定版保留
latest标签 - 为每个commit保留
sha-xxxxxx标签便于回溯
-
清理策略:
- 定期清理旧镜像(GHCR有存储限制)
- 使用GitHub API或Docker Hub API实现自动清理
-
监控方案:
- 配置仓库的webhook通知
- 对推送失败设置报警规则
-
回滚机制:
- 始终保留最近3个版本的镜像
- 在部署脚本中实现快速版本切换
这套双推送方案在我们团队实施后,部署成功率从92%提升到99.8%,特别是在跨国部署场景下,通过智能选择最近的镜像源,部署速度平均加快了40%。
