在现代软件开发中,CI/CD(持续集成/持续部署) 已经成为标配。对于容器化项目来说,最爽的体验莫过于:代码一提交,镜像自动打好并推送到仓库,随时可以拉取部署。
本文将以一个前后端分离项目(Go + Vue)为例,详细介绍如何使用 GitHub Actions 搭建一套自动化的 Docker 构建流水线,并实现 "一次构建,双重推送" ------同时推送到 GitHub Container Registry (ghcr.io) 和 Docker Hub。
🚀 为什么要做双重推送?
- GitHub Container Registry (GHCR) :
- 优势:与 GitHub 源码仓库无缝集成,拉取速度快,权限管理方便(使用 GitHub Token 即可)。
- 场景:适合内部开发、CI 流程中间产物。
- Docker Hub :
- 优势:全球最大的容器镜像库,也是默认的 Docker 拉取源。
- 场景:适合对外发布正式版本,用户 docker pull 时不需要输入长长的域名。
🛠️ 准备工作
在开始编写流水线之前,我们需要准备好 Dockerfile 并配置好安全凭证。
1. 准备 Dockerfile(多阶段构建)
为了减小镜像体积,强烈建议使用多阶段构建 (Multi-stage Build)。以下是一个典型的 Go + Vue 项目的 Dockerfile 示例:
bash
# Stage 1: 构建前端
FROM node:20-alpine AS frontend-builder
WORKDIR /web
COPY frontend/package.json ./
RUN npm install
COPY frontend/ .
RUN npm run build
# Stage 2: 构建后端
FROM golang:1.24-alpine AS backend-builder
WORKDIR /server
COPY backend/go.mod backend/go.sum ./
RUN go mod download
COPY backend/ .
# 编译为静态二进制文件
RUN CGO\_ENABLED=1 GOOS=linux go build -ldflags="-s -w" -o app-server main.go
# Stage 3: 最终运行时 (极简 Alpine)
FROM alpine:latest
WORKDIR /app
# 复制后端二进制
COPY --from=backend-builder /server/app-server .
# 复制前端静态资源
COPY --from=frontend-builder /web/dist ./dist
# 暴露端口
EXPOSE 3000
CMD ["./app-server"]
2. 配置 Docker Hub Access Token
⚠️ 警告:千万不要在 CI/CD 中直接使用你的 Docker Hub 登录密码!
- 登录 Docker Hub。
- 点击头像 -> Account Settings -> Security -> New Access Token。
- 设置描述(如 "GitHub Actions"),权限选 "Read & Write",生成并复制 Token。
3. 配置 GitHub Secrets
为了让 GitHub Actions 能登录你的 Docker Hub,需要将用户名和 Token 存入仓库密钥。
- 进入 GitHub 项目仓库 -> Settings -> Secrets and variables -> Actions。
- 点击 New repository secret ,添加以下两个变量:
- DOCKERHUB_USERNAME: 你的 Docker Hub 用户名
- DOCKERHUB_TOKEN: 刚才复制的 Token
⚙️ 编写 GitHub Actions Workflow
在项目根目录下创建文件:.github/workflows/docker-publish.yml。
这个 Workflow 实现了以下核心逻辑:
- 触发机制:代码推送到 main 分支或打上 v* 标签(如 v1.0.0)时触发。
- 权限设置:允许 Action 写入 GitHub Packages。
- 多源登录:同时登录 GHCR 和 Docker Hub。
- 智能 Tag:自动根据 Git 分支名或 Tag 名生成 Docker Tag(例如自动生成 latest)。
yaml
name: Build and Publish Docker Image
on:
push:
branches: [ "main" ] # 推送到 main 分支时触发
tags: [ 'v*.*.*' ] # 推送 v1.0.0 格式 tag 时触发
pull\_request:
branches: [ "main" ] # PR 时只构建不推送
env:
# GitHub Container Registry 配置
REGISTRY_GHCR: ghcr.io
IMAGE_NAME_GHCR: ${{ github.repository }}
# Docker Hub 配置 (格式:用户名/项目名)
IMAGE_NAME_DOCKERHUB: ${{ secrets.DOCKERHUB_USERNAME }}/nanodoc
jobs:
build-and-push:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write # 允许写入 GitHub Packages
steps:
# 1. 检出代码
- name: Checkout repository
uses: actions/checkout@v4
# 2. 设置 Docker 构建环境
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
# 3. 登录 GitHub Container Registry
- name: Log into ghcr.io
if: github.event_name != 'pull_request'
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY_GHCR }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }} # GitHub 自带 Token
# 4. 登录 Docker Hub (使用 Secrets)
- name: Log into Docker Hub
if: github.event_name != 'pull_request'
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
# 5. 提取元数据 (自动生成 Tags)
# 这步非常关键,它会同时为两个 Registry 生成对应的 tags
- name: Extract Docker metadata
id: meta
uses: docker/metadata-action@v5
with:
images: |
${{ env.REGISTRY_GHCR }}/${{ env.IMAGE_NAME_GHCR }}
${{ env.IMAGE_NAME_DOCKERHUB }}
tags: |
type=semver,pattern={{version}}
type=ref,event=branch
type=raw,value=latest,enable={{is_default_branch}}
# 6. 构建并推送
- name: Build and push Docker image
id: build-and-push
uses: docker/build-push-action@v5
with:
context: .
file: ./Dockerfile
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha # 使用 GitHub Actions 缓存加速
cache-to: type=gha,mode=max
✅ 验证与使用
配置完成后,当你下一次推送代码时,Action 就会自动运行。
场景一:日常开发推送
当你执行 git push origin main 时,Action 会构建并推送:
- ghcr.io/yourname/nanodoc:main
- yourname/nanodoc:main
场景二:发布版本
当你打上 Tag 并推送时:
bash
git tag v1.0.0
git push origin v1.0.0
Action 会构建并推送以下 Tag,非常适合生产环境使用:
- ghcr.io/yourname/nanodoc:v1.0.0
- ghcr.io/yourname/nanodoc:latest
- yourname/nanodoc:v1.0.0
- yourname/nanodoc:latest
用户如何拉取?
用户现在可以自由选择源:
bash
# 从 Docker Hub 拉取 (默认,短路径)
docker pull yourname/nanodoc:latest
bash
# 从 GitHub Packages 拉取
docker pull ghcr.io/yourname/nanodoc:latest
📝 总结
通过这套配置,我们实现了一个专业的开源项目发布流程:
- 安全性:不暴露密码,使用 Token。
- 自动化:完全由 Git 操作触发,无需人工干预。
- 多源分发:同时覆盖了 GitHub 生态用户和 Docker Hub 大众用户。
- 高效:利用 Docker Layer 缓存和 GitHub Actions 缓存,构建速度飞快。
希望这篇实战教程能帮到正在折腾 CI/CD 的你!🚀