作为一个人项目,我的博客系统包含 4 个独立仓库:后端(Spring Boot)、前端(Next.js 博客展示)、管理后台(Next.js Admin)、部署配置(Docker Compose)。最初我每次更新代码都要手动构建镜像、推送 Docker Hub、SSH 到 VPS 拉取最新版——这套流程繁琐且容易出错。
接入 GitHub Actions 后,我只需 git push,所有事情自动完成。这篇文章记录完整的 CI/CD 设计。
代码仓库(GitHub)
├── personal-blog-backend → 触发 workflow → 构建 backend 镜像
│ └── .github/workflows/
│ ├── docker-publish.yml # 主后端
│ └── docker-publish-admin-server.yml # Spring Boot Admin
├── personal-blog-frontend → 触发 workflow → 构建 frontend 镜像
│ └── .github/workflows/
│ └── docker-publish.yml
└── personal-blog-admin → 触发 workflow → 构建 admin 镜像(可选)
触发 → 构建 → 推送 → 部署(手动 pull)
目前 CI 自动完成前三步,部署阶段我选择手动 SSH 到 VPS 执行 docker compose pull && docker compose up -d,保留对生产部署的主动控制权。
所有 workflow 结构一致,以后端为例:
name: Build and Push Backend Image
on:
push:
branches: [ "main" ]
jobs:
build-and-push:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v5
- name: Log in to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build and push
uses: docker/build-push-action@v6
with:
context: .
push: true
tags: |
${{ vars.DOCKERHUB_USERNAME }}/personal-blog-backend:latest
${{ vars.DOCKERHUB_USERNAME }}/personal-blog-backend:sha-${{ github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max
每个任务都是清晰的 4 步流水线:
| 步骤 | Action | 说明 |
|---|---|---|
| Checkout | actions/checkout@v5 | 克隆代码到 Runner |
| 登录 Docker Hub | docker/login-action@v3 | Token 认证,不暴露密码 |
| 安装 Buildx | docker/setup-buildx-action@v3 | 启用高级构建能力 |
| 构建并推送 | docker/build-push-action@v6 | 核心步骤,含缓存配置 |
GitHub Actions 提供两种配置存储:
username: ${{ vars.DOCKERHUB_USERNAME }} # ✅ 非敏感,用 vars(明文可见)
password: ${{ secrets.DOCKERHUB_TOKEN }} # ✅ 敏感,用 secrets(加密存储,日志中被遮盖)
为什么不都用 secrets?
vars 的值在 Actions 日志中可见,便于调试vars 更透明secrets 只用于真正需要保密的数据(Token、密码),保持最小权限原则tags: |
${{ vars.DOCKERHUB_USERNAME }}/personal-blog-backend:latest
${{ vars.DOCKERHUB_USERNAME }}/personal-blog-backend:sha-${{ github.sha }}
两个标签的职责不同:
latest:始终指向最新构建,VPS 上 docker compose pull 直接拉取,无需修改配置文件sha-xxxxxxx:每次构建的唯一标识符,方便版本回滚回滚示例:
# VPS 上回滚到指定版本
docker pull liusxml/personal-blog-backend:sha-abc1234
# 修改 compose.yml 中的 image tag 后 up -d
cache-from: type=gha
cache-to: type=gha,mode=max
**GHA(GitHub Actions Cache)**是 Docker 官方和 GitHub 联合设计的构建缓存方案:
npm install 下载 node_modules 耗时最长,缓存 node_modules 层后速度提升明显mode=max 表示缓存所有构建层(包括中间层),缓存命中率更高。
后端仓库里有两个 workflow,因为有两个需要打包的服务:主后端和一个独立的 Spring Boot Admin 监控服务(它本质上是对 spring-boot-admin-server 的薄封装,独立成一个 JAR)。
该 workflow 的特殊之处在于指定了非默认 Dockerfile:
- name: Build and push
uses: docker/build-push-action@v6
with:
context: .
file: Dockerfile.admin-server # ← 指定非默认 Dockerfile
push: true
tags: |
${{ vars.DOCKERHUB_USERNAME }}/personal-blog-admin-server:latest
${{ vars.DOCKERHUB_USERNAME }}/personal-blog-admin-server:sha-${{ github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max
docker/build-push-action 默认寻找 ./Dockerfile,通过 file: 字段可以指定任意路径的 Dockerfile,支持一个仓库打包多个镜像。
Next.js 有一个重要特性:NEXT_PUBLIC_* 变量在构建时就被打包进客户端 JavaScript,而不是运行时读取。这意味着 API Base URL 必须在 CI 构建阶段注入:
- name: Build and push
uses: docker/build-push-action@v6
with:
context: .
push: true
tags: |
${{ vars.DOCKERHUB_USERNAME }}/personal-blog-frontend:latest
${{ vars.DOCKERHUB_USERNAME }}/personal-blog-frontend:sha-${{ github.sha }}
build-args: |
NEXT_PUBLIC_API_BASE_URL=https://api.chonkybird.com # ← 构建时注入
cache-from: type=gha
cache-to: type=gha,mode=max
这也是为什么前端不能像后端一样在运行时通过 -e 传环境变量来改变 API 地址——镜像一旦打包,URL 就固定了。
后端(Spring Boot)就没有这个问题,它在运行时读取 application.yaml 或环境变量,可以灵活配置。
不要把 Docker Hub Token 放进代码。正确做法:
DOCKERHUB_TOKEN${{ secrets.DOCKERHUB_TOKEN }} 引用,Token 永远不会出现在日志里Token 应定期轮换,尤其是公开仓库。
整套 CI/CD 的核心理念:
| 原则 | 实践 |
|---|---|
| 自动化 | push 触发,无需手动操作 |
| 可追溯 | sha 标签记录每次构建的源码版本 |
| 快速 | GHA 缓存复用依赖层,构建从 5 分钟降到 1 分钟 |
| 安全 | secrets 存储 Token,vars 存储非敏感配置 |
| 可控 | 生产部署保持手动,避免误操作 |
这套方案基于 GitHub Actions + Docker Hub + Docker Compose,全程使用免费资源,适合个人项目和小团队。
在设计个人博客后端时,我面临一个经典抉择:用简单粗暴的单体应用,还是拥抱时髦的微服务?最终我选择了两者之间的 模块化单体架构(Modular Monolith),事实证明这是正确的决定。 为什么不用微服务? 微服务固然优秀,但对于个人项目来说成本太高: - 需要服务注册、配置中心、网关、链路追踪…… - 每个服务独立部署,运维复杂度指数级增长 - 分布式事务难以处理 - 单人开发,维护多个仓库效率...
Spring Security 的默认配置很简单:一条过滤链管所有请求。但在实际项目中,不同端点往往有完全不同的认证需求。本文介绍我在个人博客项目中实现的三链 SecurityFilterChain 架构。 问题背景 博客系统的端点可以分为三类: 1. 公开白名单:/actuator/health(运维监控)、/api/v1/articles(文章列表)、Swagger 文档 2.
博客系统的管理后台需要处理文章的增删改查、评论审核、文件上传、系统监控等功能。这类界面有一个共同特点:大量表单 + 大量数据展示 + 频繁的服务端交互。 本文记录我在管理后台中选用的技术栈,以及每个选型背后的理由。 --- 技术栈总览 | 层级 | 技术 | 版本 | 用途 | |:---|:---|:---|:---| | 框架 | Next.
从数据库到用户界面,这篇文章记录我在个人博客系统中每一层技术选型背后的思考。整个系统包含 4 个独立仓库,涵盖 Java 后端、两个 Next.js 前端、和 Docker 部署配置。 --- 一、整体架构图 ` 用户浏览器 │ ▼ Caddy(反向代理 + 自动 HTTPS) │ ├── chonkybird.com → Next.js 博客前端(SSR) ├── admin.
这篇文章记录我在开发个人博客系统全程中使用 AI 工具的真实体验——包括它在哪些环节显著提升了效率,在哪些地方需要谨慎,以及如何正确地使用 AI。 整个系统包含 Spring Boot 后端、两个 Next.js 前端、Docker Compose 部署配置,共 4 个独立仓库,全程有 AI 深度介入。