记录一下云主机的搭建流程。
1. 架构与目录设计#
对于个人项目,将 API、Nginx 配置和前端代码放在一个仓库(Monorepo)中管理是最省心的方案,方便统一版本和迁移。
1.1 目录结构#
1
2
3
4
5
6
7
8
9
10
| 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 云主机上执行:
1
| curl -fsSL [https://get.docker.com](https://get.docker.com) | bash
|
2.2 编写 docker-compose.yml#
为了方便记忆和排查,采用路径对齐策略:将宿主机的 ./blog/public 挂载到容器内的 /var/www/blog/public,保持逻辑一致。
同时配置了日志大小限制,防止容器长时间运行占满服务器硬盘。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
| 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
2
| cd /var/www
git clone [https://github.com/your-username/my-project.git](https://github.com/your-username/my-project.git)
|
- 启动服务:
1
2
| cd my-project
docker compose up -d
|
3. SSL 证书解决方案#
采用 Host 模式:在宿主机申请证书,通过挂载共享给容器。
3.1 申请证书 (Certbot)#
1
2
3
4
5
| # 安装 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:
1
2
| # 每天凌晨 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 仓库整洁。
1
2
| # 在 blog 目录下
git submodule add [https://github.com/adityatelange/hugo-PaperMod](https://github.com/adityatelange/hugo-PaperMod) themes/PaperMod
|
4.2 配置文件修改 (config.yaml)#
1
2
| theme: PaperMod
baseURL: 'https://www.example.com/'
|
5. CI/CD 自动化部署 (GitHub Actions)#
为了兼顾效率与稳定,我设计了两条独立的流水线。
5.1 准备工作:配置 Secrets#
在 GitHub 仓库的 Settings -> Secrets and variables -> Actions 中添加:
HOST_IP: 云主机 IPSSH_USER: 登录用户名 (如 root)SERVER_SSH_KEY: 私钥内容 (对应服务器上的公钥)
5.2 流线一:内容更新 (deploy.yml)#
- 场景:写新博客。
- 逻辑:拉代码(含子模块)-> 编译 Hugo(
--source blog)-> 清空服务器 blog/public -> SCP 上传整棵 blog/public。 - 优势:速度快,无需重启容器。用
source: "blog/public" 配合 strip_components: 1 上传整棵树,避免只传一层导致 images/covers 等子目录缺失。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
| # .github/workflows/deploy.yml
name: Deploy to Cloud Server
on:
workflow_dispatch:
push:
branches: [master]
paths:
- 'blog/**'
- '.github/workflows/deploy.yml'
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
submodules: true
- name: Setup Hugo
uses: peaceiris/actions-hugo@v2
with:
hugo-version: 'latest'
extended: true
- name: Build
run: hugo --minify --source blog
- name: Clean Remote Directory
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.HOST_IP }}
username: ${{ secrets.SSH_USER }}
key: ${{ secrets.SERVER_SSH_KEY }}
script: |
rm -rf /var/www/my-cloud-project/blog/public/*
- name: Deploy to Server via SCP
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-cloud-project/blog"
strip_components: 1
|
5.3 流线二:架构变更 (deploy-infra.yml)#
- 场景:修改
docker-compose.yml、nginx/** 或本 workflow。 - 逻辑:SSH 登录 -> 拉取代码 -> 重建容器。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
| # .github/workflows/deploy-infra.yml
name: Deploy Infrastructure
on:
push:
branches: [master]
paths:
- 'docker-compose.yml'
- 'nginx/**'
- '.github/workflows/deploy-infra.yml'
jobs:
deploy-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-cloud-project
git fetch --all
git reset --hard origin/master
docker compose up -d --remove-orphans --build
docker image prune -f
|
6. 主机迁移指南#
这套方案最大的优势是可移植性。若需要更换服务器,仅需三步恢复:
- 环境安装:在新主机安装 Docker 和 Git。
- 数据恢复:
- 将备份的 SSL 证书目录
/etc/letsencrypt 恢复到新主机。 git clone 仓库代码。
- 一键启动:
7. 总结#
通过这套方案,我们不仅拥有了一个高性能的静态博客,更获得了一套标准化的 DevOps 基础设施:
- 工程化管理:Monorepo 让前后端与配置管理井井有条。
- 极致自动化:写文章秒级部署,改架构自动重启,彻底解放双手。
- 高稳定性:Docker 隔离环境,日志限制与自动 SSL 续期确保了服务的长期健康运行。