nginx反向代理+docker容器化部署

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.htmlfront/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