记录一下云主机的搭建流程。

1. 架构与目录设计

对于个人项目,将 API、Nginx 配置和前端代码放在一个仓库(Monorepo)中管理是最省心的方案,方便统一版本和迁移。

1.1 目录结构

my-project/               # 项目根目录
├── .github/workflows/    # CI/CD 脚本
├── api/                  # (预留) Golang API 服务
├── blog/                 # Hugo 博客源码
│   ├── public/           # 编译后的静态文件
│   └── themes/           # Git Submodule 管理的主题
├── nginx/                # Nginx 配置
│   ├── conf.d/           # 站点配置
│   └── nginx.conf        # 全局配置
└── docker-compose.yml    # 服务编排

2. 服务端环境准备 (Host)

2.1 安装 Docker

在 Linux 云主机上执行:

curl -fsSL [https://get.docker.com](https://get.docker.com) | bash

2.2 编写 docker-compose.yml

为了方便记忆和排查,采用路径对齐策略:将宿主机的 ./blog/public 挂载到容器内的 /var/www/blog/public,保持逻辑一致。

同时配置了日志大小限制,防止容器长时间运行占满服务器硬盘。

version: '3.8'

services:
  nginx:
    image: nginx:alpine
    container_name: my-nginx
    ports:
      - "80:80"
      - "443:443"
    volumes:
      # 配置挂载
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
      - ./nginx/conf.d:/etc/nginx/conf.d:ro
      # 站点挂载 (关键:路径对齐,避免 404/403)
      - ./blog/public:/var/www/blog/public:ro
      # SSL 证书挂载 (直接使用宿主机证书)
      - /etc/letsencrypt:/etc/letsencrypt:ro
      # ACME 验证目录
      - ./nginx/html:/usr/share/nginx/html
    
    # 生产环境必备:限制日志大小
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"
    restart: always

2.3 首次部署与启动

在配置 CI/CD 之前,需要先手动在服务器上把服务跑起来。

  1. 拉取代码
cd /var/www
git clone [https://github.com/your-username/my-project.git](https://github.com/your-username/my-project.git)
  1. 启动服务
cd my-project
docker compose up -d

3. SSL 证书解决方案

采用 Host 模式:在宿主机申请证书,通过挂载共享给容器。

3.1 申请证书 (Certbot)

# 安装 certbot
apt-get install certbot  # 或 yum install certbot

# 首次申请 (替换为你的实际域名)
certbot certonly --webroot -w /var/www/my-project/nginx/html -d [www.example.com](https://www.example.com)

3.2 自动续期 (Cron)

Let’s Encrypt 有效期 90 天,配置 Cron 任务实现自动续期并重载 Nginx。

编辑定时任务 crontab -e

# 每天凌晨 3 点检查,只有续期成功才重载 Nginx (无需重启容器)
0 3 * * * certbot renew --quiet --deploy-hook "docker compose -f /var/www/my-project/docker-compose.yml exec nginx nginx -s reload"

4. Hugo 本地开发流

4.1 初始化与主题

使用 git submodule 管理主题,好处是能轻松拉取主题的后续更新,且保持 Git 仓库整洁。

# 在 blog 目录下
git submodule add [https://github.com/adityatelange/hugo-PaperMod](https://github.com/adityatelange/hugo-PaperMod) themes/PaperMod

4.2 配置文件修改 (hugo.toml)

theme = 'PaperMod'
baseURL = '[https://www.example.com/](https://www.example.com/)'

5. CI/CD 自动化部署 (GitHub Actions)

为了兼顾效率与稳定,我设计了两条独立的流水线。

5.1 准备工作:配置 Secrets

在 GitHub 仓库的 Settings -> Secrets and variables -> Actions 中添加:

  • HOST_IP: 云主机 IP
  • SSH_USER: 登录用户名 (如 root)
  • SERVER_SSH_KEY: 私钥内容 (对应服务器上的公钥)

5.2 流线一:内容更新 (deploy.yml)

  • 场景:写新博客。
  • 逻辑:编译 Hugo -> SCP 传输文件。
  • 优势:速度快,无需重启容器
# .github/workflows/deploy.yml
name: Deploy Content
on:
  push:
    paths: ['blog/**'] # 只监听博客目录

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
        with:
          submodules: true # 必须拉取主题子模块

      - name: Setup Hugo
        uses: peaceiris/actions-hugo@v2
        with:
          hugo-version: 'latest'
          extended: true

      - name: Build
        run: cd blog && hugo --minify

      - name: Deploy to Server
        uses: appleboy/scp-action@master
        with:
          host: ${{ secrets.HOST_IP }}
          username: ${{ secrets.SSH_USER }}
          key: ${{ secrets.SERVER_SSH_KEY }}
          source: "blog/public/"
          target: "/var/www/my-project/"
          strip_components: 0

5.3 流线二:架构变更 (deploy-infra.yml)

  • 场景:修改 nginx.confdocker-compose.yml
  • 逻辑:SSH 登录 -> 拉取代码 -> 重建容器。
# .github/workflows/deploy-infra.yml
name: Deploy Infrastructure
on:
  push:
    paths:
      - 'docker-compose.yml'
      - 'nginx/**'

jobs:
  update-infra:
    runs-on: ubuntu-latest
    steps:
      - name: SSH Remote Commands
        uses: appleboy/ssh-action@master
        with:
          host: ${{ secrets.HOST_IP }}
          username: ${{ secrets.SSH_USER }}
          key: ${{ secrets.SERVER_SSH_KEY }}
          script: |
            cd /var/www/my-project
            # 强制同步代码
            git fetch --all
            git reset --hard origin/master
            # 重建容器 (应用新配置)
            docker compose up -d --remove-orphans --build
            # 清理垃圾镜像
            docker image prune -f

6. 主机迁移指南

这套方案最大的优势是可移植性。若需要更换服务器,仅需三步恢复:

  1. 环境安装:在新主机安装 Docker 和 Git。
  2. 数据恢复
  • 将备份的 SSL 证书目录 /etc/letsencrypt 恢复到新主机。
  • git clone 仓库代码。
  1. 一键启动
docker compose up -d

7. 总结

通过这套方案,我们不仅拥有了一个高性能的静态博客,更获得了一套标准化的 DevOps 基础设施

  1. 工程化管理:Monorepo 让前后端与配置管理井井有条。
  2. 极致自动化:写文章秒级部署,改架构自动重启,彻底解放双手。
  3. 高稳定性:Docker 隔离环境,日志限制与自动 SSL 续期确保了服务的长期健康运行。