Blog 后端 Docker 部署总结
服务器环境:阿里云 ECS(低配),已预装 Docker
已预部署中间件:mysql8.0、redis6.2、minio、nginx-container
域名:wzx.glaty.cn
一、部署架构
浏览器 ──▶ wzx.glaty.cn:80 ──▶ nginx-container
│
┌──────────────────────┼──────────────────────┐
▼ ▼ ▼
/api/* → blog-app:8083 /files/* → minio:9000 其他 → 静态文件
所有容器通过 blog-net(自定义 Docker 网络)互相通信。
以容器名作为 hostname,例如 blog-app 连接 mysql8.0:3306。
二、核心文件清单
| 文件 | 用途 |
|---|---|
Dockerfile |
极简镜像(只 COPY 预编译的二进制,不含 Go 编译器) |
docker-compose.yml |
定义 blog-app 服务,环境变量从 .env 读取 |
.env |
敏感配置(密码、容器名等),服务器独有,不入 git |
blog-nginx.conf |
Nginx 反向代理规则,挂载到 nginx-container |
configs/config.toml |
Go 应用配置模板(环境变量可覆盖) |
三、代码层面改造
1. 配置加载弹性化(config.go(file:///d:/GoLang/GoProgram/Blog/internal/config/config.go))
问题 :原配置写死 Windows 绝对路径 D:\GoLang\GoProgram\Blog\configs\config.toml。
解决:三级加载 + 环境变量覆盖
① CONFIG_PATH 环境变量(优先级最高,Docker 可指定)
② 相对路径 "configs/config.toml"(Docker WORKDIR /app 下自动找到)
③ 可执行文件同目录(兜底)
go
// 27 个环境变量可覆盖 TOML 配置
APP_HOST APP_PORT DB_HOST DB_PORT DB_USER DB_PASSWORD DB_NAME
JWT_SECRET REDIS_HOST REDIS_PORT
MINIO_ENDPOINT MINIO_PUBLIC_URL MINIO_ACCESS_KEY MINIO_SECRET_KEY MINIO_BUCKET
2. MinIO 内网/公网 URL 解耦(oss.go(file:///d:/GoLang/GoProgram/Blog/internal/pkg/minio/oss.go))
问题 :上传接口用内网地址 minio:9000 连 MinIO,但返回给前端的 URL 也需要是公网可达的。
解决 :新增 MINIO_PUBLIC_URL 环境变量
内网通信:minio:9000(Docker 网络内)
公网 URL:http://wzx.glaty.cn/files(Nginx 代理后对外暴露)
go
publicEndpoint = os.Getenv("MINIO_PUBLIC_URL")
// 上传成功后返回: http://wzx.glaty.cn/files/blog/avatar/xxx.jpg
url := fmt.Sprintf("%s/%s/%s", publicEndpoint, cfg.BucketName, objectName)
四、本地编译 + 极简镜像(方案 B)
为什么不用服务器编译?
服务器内存太小,go build 编译时内存飙升 → OOM 宕机。
解决思路
Go 交叉编译: 在本地 Windows(内存充足)编译出 Linux 可执行文件,服务器只需要 COPY 进镜像。
操作步骤
powershell
# ========== Windows 本地 ==========
cd D:\GoLang\GoProgram\Blog
# 1. 交叉编译(产出 Linux 二进制)
$env:GOOS="linux"
$env:GOARCH="amd64"
$env:CGO_ENABLED="0"
go build -ldflags="-s -w" -o blog-app cmd/main.go
# 2. 打包(不含源码,不含 .env)
tar -czf blog-deploy.tar.gz blog-app configs docker-compose.yml Dockerfile
# 3. 上传到服务器
scp blog-deploy.tar.gz root@wzx.glaty.cn:/root/
bash
# ========== 服务器端 ==========
cd /home/www/project/Blog
# 第一次:备份 .env
cp .env /home/www/project/.env.blog # 只做一次
# 清空旧文件
rm -rf *
# 解压
tar -xzf /root/blog-deploy.tar.gz
# 恢复 .env
cp /home/www/project/.env.blog .env
# 启动
docker compose up -d --build
Dockerfile 关键点
dockerfile
FROM alpine:3.21 # 只拉 7MB 基础镜像
COPY blog-app . # 拷贝预编译二进制
RUN chmod +x blog-app # ★ Windows 交叉编译后必须加这行
COPY configs/ ./configs/
CMD ["./blog-app"]
不含 Go 编译器,服务器构建只需 1-2 秒,内存消耗 < 20MB。
五、环境变量(.env)
bash
# 服务器上创建(只一次,永远不动它)
# /home/www/project/Blog/.env
DB_HOST=mysql8.0
DB_USER=root
DB_PASSWORD=你的密码
DB_NAME=blog
REDIS_HOST=redis6.2
MINIO_ENDPOINT=minio:9000
MINIO_PUBLIC_URL=http://wzx.glaty.cn/files
MINIO_ACCESS_KEY=minioadmin
MINIO_SECRET_KEY=minioadmin123
MINIO_BUCKET=blog
JWT_SECRET=你的JWT密钥
六、Nginx 配置(blog-nginx.conf)
nginx
server {
listen 80;
server_name wzx.glaty.cn;
# Docker 内部 DNS,让变量式 proxy_pass 能延迟解析
resolver 127.0.0.11 valid=30s ipv6=off;
# 前台(用户端)
location / {
root /usr/share/nginx/html/blog/front;
index index.html index.htm;
try_files $uri $uri/ /index.html; # SPA 路由
}
# 后台管理
location /admin {
root /usr/share/nginx/html/blog;
try_files $uri $uri/ /admin/index.html;
}
# API 代理 → blog-app
location /api/ {
set $backend_api blog-app:8083;
proxy_pass http://$backend_api;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
# MinIO 文件代理(直接式 proxy_pass,不用变量)
location /files/ {
proxy_pass http://minio:9000/;
proxy_set_header Host $host;
}
}
挂载方式
bash
# 宿主机编辑文件
vim /root/nginx/conf.d/blog.conf
# 热重载(不重启容器)
docker exec nginx-container nginx -s reload
七、踩坑记录
坑 1:Docker 构建导致服务器 OOM 宕机 🚨
| 现象 | docker compose up -d --build 执行中服务器崩溃重启 |
|---|---|
| 原因 | Go 编译器并发编译消耗大量内存 |
| 解决 | 方案 B:本地交叉编译 + 极简 Dockerfile,服务器不再运行 Go 编译器 |
坑 2:permission denied ------ Windows 编译的二进制无执行权限
| 现象 | docker compose up 后容器反复重启,日志 exec: "./blog-app": permission denied |
|---|---|
| 原因 | Windows 文件系统不保存 Linux 的 +x 权限位 |
| 解决 | Dockerfile 中加一行 RUN chmod +x blog-app |
坑 3:blog-app 重启循环 ------ MinIO 初始化后崩溃
| 现象 | 日志显示 MinIO 初始化成功,然后 exited with code 1 重启 |
|---|---|
| 原因 | MySQL 数据库 blog 不存在(手动容器不会自动建库) |
| 解决 | docker exec mysql8.0 mysql -uroot -p -e "CREATE DATABASE blog CHARACTER SET utf8mb4;" → 再导入 SQL |
坑 4:各容器不在同一网络,互相不通
| 现象 | 环境变量配了容器名就是连不上 |
|---|---|
| 原因 | blog-app 在 blog-net,但 mysql8.0 / redis6.2 / minio 还在默认 bridge 网络 |
| 解决 | docker network connect blog-net mysql8.0 / redis6.2 / minio |
坑 5:Nginx 403 Forbidden ------ 前端 dist 目录嵌套
| 现象 | 访问 http://wzx.glaty.cn 显示 403 |
|---|---|
| 原因 | dist 包解压后 index.html 在 front/dist/ 子目录,Nginx 找不到 |
| 解决 | mv /root/nginx/blog/front/dist/* /root/nginx/blog/front/ 提一层 |
坑 6:Nginx 代理 MinIO 出现 307 重定向到控制台
| 现象 | 图片地址在浏览器打开 → 307 → 跳转 wzx.glaty.cn:9001/dashboard |
|---|---|
| 原因 | 变量式 proxy_pass 不剥离 location 前缀 ,/files/ 被原样传给 MinIO,导致路径错误后 MinIO 重定向 |
| 解决 | /files/ location 改用直接式 proxy_pass http://minio:9000/;(不经过变量) |
nginx
# ❌ 变量式:/files/blog/avatar/xxx.jpg → minio:9000/blog/blog/avatar/xxx.jpg
set $backend_minio minio:9000;
proxy_pass http://$backend_minio/;
# ✅ 直接式:/files/blog/avatar/xxx.jpg → minio:9000/blog/avatar/xxx.jpg
proxy_pass http://minio:9000/;
坑 7:头像存储路径两次含 blog/ 前缀
| 现象 | 上传头像成功但前端展示不出来 |
|---|---|
| 原因 | proxy_pass http://$backend_minio/blog/ + /files/ 保留的前缀 = /blog/blog/... |
| 解决 | proxy_pass 改为 http://minio:9000/,路径 /blog/ 由请求 URI 自带 |
坑 8:.env 被 rm -rf * 误删
| 现象 | 更新部署后容器重启循环 |
|---|---|
| 原因 | 解压前 rm -rf * 把 .env 一起删了,环境变量回退默认值 |
| 解决 | 部署前先 cp .env 到上级目录,解压完拷回来 |
八、日常更新流程
powershell
# ========== 本地 Windows(每次改代码后) ==========
cd D:\GoLang\GoProgram\Blog
$env:GOOS="linux"; $env:GOARCH="amd64"; $env:CGO_ENABLED="0"
go build -ldflags="-s -w" -o blog-app cmd/main.go
tar -czf blog-deploy.tar.gz blog-app configs docker-compose.yml Dockerfile
scp blog-deploy.tar.gz root@wzx.glaty.cn:/root/
bash
# ========== 服务器 ==========
cd /home/www/project/Blog
cp .env ../.env.blog # 备份 .env
rm -rf *
tar -xzf /root/blog-deploy.tar.gz
cp ../.env.blog .env # 恢复 .env
docker compose down
docker compose up -d --build
docker compose logs -f # 确认启动
九、常用命令速查
bash
# 查看容器状态
docker ps
# 查看日志
docker compose logs -f # blog-app 日志
docker logs nginx-container # Nginx 日志
docker exec mysql8.0 mysql -uroot -p # 进 MySQL
# 网络管理
docker network ls
docker network inspect blog-net
docker network connect blog-net <容器名>
# Nginx 热重载(改完配置文件立即生效)
docker exec nginx-container nginx -s reload
# 进 MySQL 导入 SQL
docker exec -i mysql8.0 mysql -uroot -p blog < /root/blog.sql
# 停止 / 重建
docker compose down
docker compose up -d --build