最终效果
博客通过域名访问,HTTPS 加密
Go 后端 + Next.js 前端 + PostgreSQL + Redis,全容器化部署
Nginx 反向代理,Certbot 自动续期 SSL 证书
支持 CDN 加速
前置条件
项目 | 说明 |
|---|---|
阿里云 ECS | Ubuntu 22.04/24.04,2H2G 起步 |
域名 | 已备案,DNS 解析到服务器 IP |
GitHub 仓库 | 已配置好 GitHub Actions 自动构建镜像 |
一、服务器准备
1.1 安全组放行端口
这是最容易忘的一步,不放行端口后面全白搭。
PLAINTEXT
阿里云控制台 → ECS → 实例 → 点击实例 → 安全组 → 点击安全组ID → 配置规则 → 入方向 → 手动添加
协议 | 端口范围 | 源 | 说明 |
|---|---|---|---|
TCP | 22 | 0.0.0.0/0 | SSH |
TCP | 80 | 0.0.0.0/0 | HTTP |
TCP | 443 | 0.0.0.0/0 | HTTPS |
1.2 DNS 解析
PLAINTEXT
域名管理后台 → 添加 A 记录
blog.yourdomain.com → A → 你的服务器IP
1.3 SSH 连接服务器
BASH
ssh root@你的服务器IP
1.4 系统更新
BASH
sudo apt update && sudo apt upgrade -y
二、安装 Docker
如果你是阿里云的服务器,可以参考官方的文档
2.1 安装 Docker Engine
BASH
# 卸载旧版本(如果有)
sudo apt remove -y docker docker-engine docker.io containerd runc
# 安装依赖
sudo apt install -y ca-certificates curl gnupg
# 添加 Docker 官方 GPG key
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg
# 添加 Docker 仓库
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
# 安装 Docker
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
# 验证
docker --version
docker compose version
2.2 配置 Docker 镜像加速(国内服务器必做)
BASH
sudo mkdir -p /etc/docker
sudo tee /etc/docker/daemon.json <<'EOF'
{
"registry-mirrors": [
"https://docker.1ms.run",
"https://docker.xuanyuan.me"
]
}
EOF
sudo systemctl daemon-reload
sudo systemctl restart docker
2.3 登录阿里云容器镜像服务
BASH
docker login --username=你的阿里云账号 registry.cn-hangzhou.aliyuncs.com
# 输入密码:你的阿里云 ACR 密码(不是登录密码,在 ACR 控制台设置)
三、安装 PostgreSQL
3.1 安装
BASH
sudo apt install -y postgresql postgresql-contrib
3.2 创建数据库和用户
BASH
# 切换到 postgres 用户
sudo -u postgres psql
# 在 psql 中执行以下命令(注意替换密码)
CREATE USER hr_blog WITH PASSWORD '你的数据库密码';
CREATE DATABASE hr_blog OWNER hr_blog;
GRANT ALL PRIVILEGES ON DATABASE hr_blog TO hr_blog;
# 退出
\q
3.3 配置远程连接(让 Docker 容器能连上)
BASH
# 修改 pg_hba.conf
sudo nano /etc/postgresql/*/main/pg_hba.conf
在文件末尾添加:
PLAINTEXT
# 允许 Docker 网段连接
host hr_blog hr_blog 172.16.0.0/12 md5
host hr_blog hr_blog 127.0.0.1/32 md5
BASH
# 修改 postgresql.conf
sudo nano /etc/postgresql/*/main/postgresql.conf
找到 listen_addresses,改为:
PLAINTEXT
listen_addresses = '*'
3.4 调优(2G 内存服务器)
BASH
sudo nano /etc/postgresql/*/main/postgresql.conf
修改以下参数:
PLAINTEXT
shared_buffers = 128MB
effective_cache_size = 256MB
work_mem = 4MB
maintenance_work_mem = 64MB
3.5 重启 PostgreSQL
BASH
sudo systemctl restart postgresql
3.6 验证
BASH
psql -h 127.0.0.1 -U hr_blog -d hr_blog
# 输入密码,能连上就 OK
# 输入 \q 退出
四、安装 Redis
4.1 安装
BASH
sudo apt install -y redis-server
4.2 配置
BASH
sudo nano /etc/redis/redis.conf
修改以下参数:
PLAINTEXT
# 设置密码(取消注释并修改)
requirepass 你的Redis密码
# 限制内存(2G 服务器给 64MB 够了)
maxmemory 64mb
maxmemory-policy allkeys-lru
# 关闭持久化(Redis 只是缓存,丢了无所谓,减少磁盘 IO)
save ""
4.3 重启 Redis
BASH
sudo systemctl restart redis-server
4.4 验证
BASH
redis-cli -a 你的Redis密码 ping
# 返回 PONG 就 OK
五、部署应用
5.1 创建项目目录
BASH
sudo mkdir -p /opt/hr-blog
cd /opt/hr-blog
5.2 创建 docker-compose 配置
BASH
sudo nano /opt/hr-blog/docker-compose.yml
写入以下内容(替换镜像地址为你的):
YAML
services:
hr-blog:
image: registry.cn-hangzhou.aliyuncs.com/zenghr/hr-blog-backend:latest
container_name: hr-blog-backend
restart: always
mem_limit: 800m
ports:
- "8091:8091"
- "3000:3000"
environment:
- TZ=Asia/Shanghai
- ANHEYU_DATABASE_TYPE=postgres
- ANHEYU_DATABASE_HOST=host.docker.internal
- ANHEYU_DATABASE_PORT=5432
- ANHEYU_DATABASE_USER=${ANHEYU_DATABASE_USER:-hr_blog}
- ANHEYU_DATABASE_NAME=${ANHEYU_DATABASE_NAME:-hr_blog}
- ANHEYU_DATABASE_PASSWORD=${ANHEYU_DATABASE_PASSWORD}
- ANHEYU_REDIS_ADDR=host.docker.internal:6379
- ANHEYU_REDIS_PASSWORD=${ANHEYU_REDIS_PASSWORD}
volumes:
- ./data:/anheyu/data
- ./themes:/anheyu/themes
- ./static:/anheyu/static
- ./backup:/anheyu/backup
extra_hosts:
- "host.docker.internal:host-gateway"
5.3 创建环境变量文件
BASH
sudo nano /opt/hr-blog/.env
写入(替换为你的实际密码):
PLAINTEXT
ANHEYU_DATABASE_USER=hr_blog
ANHEYU_DATABASE_NAME=hr_blog
ANHEYU_DATABASE_PASSWORD=你的数据库密码
ANHEYU_REDIS_PASSWORD=你的Redis密码
5.4 拉取镜像并启动
BASH
cd /opt/hr-blog
# 拉取最新镜像
docker compose pull
# 启动
docker compose up -d
5.5 验证应用是否启动
BASH
# 查看容器状态
docker compose -f docker-compose.yml ps
# 查看日志
docker compose -f docker-compose.yml logs -f hr-blog
# 测试 Go 后端
curl -I http://127.0.0.1:8091
# 测试 Next.js 前端
curl -I http://127.0.0.1:3000
如果两个都返回 200 OK,说明应用启动成功。按 Ctrl+C 退出日志。
六、安装 Nginx
6.1 安装
BASH
sudo apt install -y nginx
6.2 删除默认站点
BASH
sudo rm -f /etc/nginx/sites-enabled/default
6.3 创建博客站点配置
BASH
sudo nano /etc/nginx/sites-available/hr-blog.conf
写入(替换 blog.yourdomain.com 为你的域名):
NGINX
server {
listen 80;
server_name blog.yourdomain.com;
client_max_body_size 50m;
location / {
proxy_pass http://127.0.0.1:8091;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 120s;
proxy_buffering off;
}
access_log /var/log/nginx/hr-blog-access.log;
error_log /var/log/nginx/hr-blog-error.log;
}
6.4 启用站点配置
BASH
sudo ln -s /etc/nginx/sites-available/hr-blog.conf /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
6.5 验证
此时用浏览器访问 http://blog.yourdomain.com,应该能看到博客了。
七、配置 HTTPS(Certbot 自动申请 SSL 证书)
7.1 安装 Certbot
BASH
sudo apt install -y certbot python3-certbot-nginx
7.2 申请证书
BASH
sudo certbot --nginx -d blog.yourdomain.com
按提示操作:
输入邮箱 — 用于证书过期提醒
同意服务条款 — 输入
Y是否分享邮箱 — 输入
N选择重定向方式 — 选
2(重定向 HTTP 到 HTTPS)
Certbot 会自动修改 Nginx 配置,添加 SSL 证书和 HTTP → HTTPS 重定向。
7.3 验证自动续期
BASH
# 查看证书信息
sudo certbot certificates
# 测试自动续期
sudo certbot renew --dry-run
输出 Congratulations, all simulated renewals succeeded 就没问题。证书 90 天有效,Certbot 会在到期前自动续期,完全不用管。
7.4 访问验证
浏览器访问 https://blog.yourdomain.com,看到小锁图标就 OK 了。
八、服务器优化
8.1 添加 Swap(防止内存不足 OOM)
2G 内存跑这些服务比较紧张,加个 Swap 更稳妥:
BASH
# 创建 2G swap 文件
sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
# 开机自动挂载
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
# 降低 swap 使用倾向(优先用物理内存)
echo 'vm.swappiness=10' | sudo tee -a /etc/sysctl.conf
sudo sysctl -p
8.2 Nginx 开启 gzip 压缩
BASH
sudo nano /etc/nginx/nginx.conf
在 http {} 块内添加:
NGINX
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_min_length 1024;
gzip_types
text/plain
text/css
text/xml
text/javascript
application/json
application/javascript
application/xml
application/rss+xml
image/svg+xml;
BASH
sudo nginx -t && sudo systemctl reload nginx
效果:HTML/CSS/JS 传输体积减少 60-80%。
8.3 Nginx 静态资源缓存
编辑站点配置:
BASH
sudo nano /etc/nginx/sites-available/hr-blog.conf
在 server 块中添加缓存规则(在现有的 location / 之前):
NGINX
# Next.js 带哈希的静态资源,长期缓存
location /_next/static/ {
proxy_pass http://127.0.0.1:8091;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
expires 365d;
add_header Cache-Control "public, immutable";
}
# 图片文件,中期缓存
location /f/ {
proxy_pass http://127.0.0.1:8091;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
expires 30d;
add_header Cache-Control "public";
}
# 静态资源
location /static/ {
proxy_pass http://127.0.0.1:8091;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
expires 7d;
add_header Cache-Control "public";
}
BASH
sudo nginx -t && sudo systemctl reload nginx
九、CDN 加速(可选但推荐)
如果你的用户分布在全国各地,CDN 能显著提升访问速度。
9.1 开通阿里云 CDN
PLAINTEXT
阿里云控制台 → CDN → 添加域名 → blog.yourdomain.com
业务类型:图片小文件
源站地址:你的服务器 IP
源站端口:80 和 443
回源协议:协议跟随
9.2 配置缓存规则
PLAINTEXT
CDN 控制台 → 域名管理 → blog.yourdomain.com → 缓存配置
路径 | 缓存时间 | 优先级 |
|---|---|---|
/_next/static/ | 365 天 | 高 |
/f/ | 30 天 | 中 |
/static/ | 7 天 | 中 |
/api/ | 不缓存 | 高 |
/ | 5 分钟 | 低 |
9.3 修改 DNS 解析
PLAINTEXT
原来: blog.yourdomain.com → A → 服务器IP
改为: blog.yourdomain.com → CNAME → CDN 分配的域名(在 CDN 控制台查看)
9.4 Nginx 信任 CDN 回源 IP
CDN 回源时,真实客户端 IP 在 X-Forwarded-For 头中,需要在 Nginx 中还原:
BASH
sudo nano /etc/nginx/sites-available/hr-blog.conf
在 server 块开头添加:
NGINX
set_real_ip_from 0.0.0.0/0;
real_ip_header X-Forwarded-For;
real_ip_recursive on;
BASH
sudo nginx -t && sudo systemctl reload nginx
十、日常运维
更新博客版本
当你推送新 tag 后,GitHub Actions 自动构建新镜像,服务器上执行:
BASH
cd /opt/hr-blog
# 拉取最新镜像
docker compose -f docker-compose.yml pull
# 重建容器(自动使用新镜像)
docker compose -f docker-compose.yml up -d
或者使用 sh 命令快速更新博客版本
📜 update-blog.sh
BASH
#!/bin/bash
#===========================================
# 博客零中断更新脚本
# 功能:拉取最新镜像 → 启动新容器 → 清理旧镜像
# 用法: ./update-blog.sh
#===========================================
set -e # 任何命令失败则立即退出
# === 配置区 ===
PROJECT_DIR="$(dirname "$0")" # 脚本所在目录(即 docker-compose.yml 所在目录)
COMPOSE_FILE="docker-compose.yml" # 如有不同,请修改
# ================
echo "📁 进入项目目录: $PROJECT_DIR"
cd "$PROJECT_DIR"
echo "🐳 拉取最新镜像..."
docker compose -f "$COMPOSE_FILE" pull
echo "🚀 用最新镜像重新部署服务(会自动重建容器)..."
docker compose -f "$COMPOSE_FILE" up -d
echo "🧹 清理旧的悬空镜像(不删除任何正在使用的镜像)..."
docker image prune -f
echo "✅ 更新完成!博客已运行在最新版本。"
查看日志
BASH
# 实时查看应用日志
docker compose -f docker-compose.yml logs -f zenghr
# 查看最近 100 行
docker compose -f docker-compose.yml logs --tail 100 zenghr
# 查看 Next.js 前端日志
docker exec -it hr-blog-backend cat /anheyu/frontend/frontend.log
# Nginx 日志
sudo tail -f /var/log/nginx/hr-blog-access.log
sudo tail -f /var/log/nginx/hr-blog-error.log
📜 blog-logs.sh
BASH
#!/bin/bash
#===========================================
# 博客日志快速查看脚本
# 用法:
# ./blog-logs.sh 实时跟踪日志
# ./blog-logs.sh -f 实时跟踪日志
# ./blog-logs.sh --tail N 查看最近 N 行
# ./blog-logs.sh -h 显示帮助
#===========================================
# === 配置区(按实际情况修改)===
SERVICE_NAME="hr-blog" # 如果服务名还是 zenghr,请改回
COMPOSE_FILE="docker-compose.yml"
PROJECT_DIR="$(dirname "$0")" # 脚本所在目录即为项目根目录
# ==============================
cd "$PROJECT_DIR" || { echo "❌ 无法进入目录 $PROJECT_DIR"; exit 1; }
# 检查 docker/compose 是否可用
if ! docker compose version &> /dev/null; then
echo "❌ 未找到 docker compose 命令,请安装 Docker Compose v2"
exit 1
fi
# 根据参数执行不同动作
case "$1" in
-f)
echo "📖 实时跟踪 $SERVICE_NAME 日志(Ctrl+C 退出)..."
docker compose logs -f "$SERVICE_NAME"
;;
--tail)
if [ -z "$2" ]; then
echo "用法: $0 --tail <行数>"
exit 1
fi
echo "📋 显示 $SERVICE_NAME 最近 $2 行日志:"
docker compose logs --tail "$2" "$SERVICE_NAME"
;;
-h|--help)
echo "用法: $(basename "$0") [选项]"
echo "选项:"
echo " 无参数 / -f 实时跟踪日志"
echo " --tail N 查看最近 N 行日志"
echo " -h, --help 显示本帮助"
;;
*)
# 默认行为:实时跟踪
echo "📖 实时跟踪 $SERVICE_NAME 日志(Ctrl+C 退出)..."
docker compose logs -f "$SERVICE_NAME"
;;
esac
重启服务
BASH
# 重启应用容器
docker compose -f docker-compose.yml restart
# 重启 Nginx
sudo systemctl restart nginx
# 重启 PostgreSQL
sudo systemctl restart postgresql
# 重启 Redis
sudo systemctl restart redis-server
数据备份
BASH
# 备份 PostgreSQL
sudo -u postgres pg_dump hr_blog > /opt/hr-blog/backup/hr_blog_$(date +%Y%m%d).sql
# 备份应用数据
tar -czf /opt/hr-blog/backup/data_$(date +%Y%m%d).tar.gz /opt/hr-blog/data
踩坑记录
1. 安全组没放行端口
最常见的问题。阿里云默认不开放 80/443 端口,必须在安全组中手动添加。症状是服务器内部 curl 正常,但外网访问不了。
2. .env 文件缺失
docker-compose.yml 中引用了 ${ANHEYU_DATABASE_PASSWORD} 等环境变量,如果 .env 文件不存在或变量为空,容器启动会报连接数据库失败。
3. PostgreSQL 拒绝 Docker 容器连接
Docker 容器通过 host.docker.internal 访问宿主机,来源 IP 是 Docker 网段(172.16.0.0/12),需要在 pg_hba.conf 中放行。
4. Nginx 显示 Welcome 页面
说明请求匹配到了默认站点,而不是你的博客配置。删除 /etc/nginx/sites-enabled/default 即可。
5. 证书申请失败
确保域名 DNS 已解析到服务器 IP,且 80 端口可从外网访问。Certbot 需要通过 HTTP 验证域名所有权。
写在最后
整个部署过程看起来步骤很多,但大部分都是一次性的配置。日常使用只需要两条命令就能更新版本:
BASH
docker compose -f docker-compose.yml pull
docker compose -f docker-compose.yml up -d
2H2G 的小机器跑这套服务完全够用,加上 Swap + gzip + CDN,访问速度也能接受。如果后续流量上来了,再升级配置也不迟。


