华为云 ECS + Docker + Nginx 部署前后端分离项目实战
写在前面
前后端分离项目在本地开发很顺畅,真正上线时却常常被这些问题卡住:服务器端口不通、静态资源 404、接口 502、刷新页面白屏。本文以华为云弹性云服务器 ECS 为例,用 Docker Compose 管理 Node.js 后端和 Nginx 前端,记录一次可复用的部署过程。
适合人群:有一台云服务器、了解基本 Linux 命令、想把个人项目部署上线的开发者。
一、准备清单
- 华为云 ECS 一台:Ubuntu 22.04,2 核 4G 起步,绑定弹性公网 IP
- 安全组入方向放通 22(SSH)、80(HTTP)、443(HTTPS)
- 一个完成备案的域名(国内节点需要)
- 本地构建好的前端 dist 目录和后端代码
这里有个容易忽略的点:后端 3000 端口不要对公网开放。浏览器所有请求都走 Nginx 同域转发,既少暴露一个端口,也顺带解决了跨域。
二、初始化服务器
SSH 登录后先更新系统并安装 Docker:
sudo apt update && sudo apt upgrade -y
sudo apt install -y docker.io docker-compose-plugin
sudo systemctl enable --now docker
sudo usermod -aG docker $USER
执行完最后一条需要重新登录,组权限才会生效。验证:
docker version
docker compose version
三、规划目录结构
把部署相关文件集中在一个目录,后续迁移或备份都方便:
/deploy
├── docker-compose.yml
├── nginx
│ └── default.conf
└── web
└── dist # 前端构建产物
四、后端容器化
在项目根目录添加 Dockerfile:
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY . .
EXPOSE 3000
CMD ["node", "server.js"]
npm ci 比 npm install 更适合构建镜像,它会严格按照 lock 文件安装,构建结果可复现。构建并打上明确版本号:
docker build -t demo-api:1.0 .
不建议使用 latest 标签,回滚时你会感谢版本号。
后端最好再提供一个健康检查接口,例如 /api/health 返回 {"status":"ok"},后面排查问题会用到。
五、配置 Nginx
nginx/default.conf:
server {
listen 80;
server_name example.com;
root /usr/share/nginx/html;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
location /api/ {
proxy_pass http://api:3000/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;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 60s;
}
gzip on;
gzip_types text/css application/javascript application/json image/svg+xml;
}
两个关键点:
try_files ... /index.html是 history 路由不白屏的原因。刷新/user/123时服务器找不到对应文件,要回退给前端路由处理。proxy_pass末尾的斜杠会影响路径拼接。写成http://api:3000/api/时,请求/api/user会被转发为/api/user;去掉末尾斜杠则行为不同。上线前用 curl 确认一次最稳妥。
六、编写 docker-compose.yml
services:
api:
image: demo-api:1.0
restart: always
environment:
- NODE_ENV=production
expose:
- "3000"
web:
image: nginx:1.27-alpine
restart: always
ports:
- "80:80"
volumes:
- ./nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
- ./web/dist:/usr/share/nginx/html:ro
depends_on:
- api
说明:
expose只把端口暴露给 Compose 内部网络,不映射到宿主机,外网无法直接访问。- 配置和静态资源都以只读方式挂载,容器内进程无法误改文件。
depends_on只保证启动顺序,不保证后端已就绪,生产环境可以再加健康检查或启动重试脚本。
七、启动与验证
docker compose up -d
docker compose ps
curl -I http://127.0.0.1
curl http://127.0.0.1/api/health
按顺序检查:容器是否 Up、Nginx 是否返回 200、接口是否返回健康状态。最后用浏览器访问公网 IP 或域名。
常见问题对照:
| 现象 | 可能原因 | 排查方向 |
|---|---|---|
| 502 Bad Gateway | 后端未启动或服务名写错 | docker compose logs api |
| 静态资源 404 | dist 路径挂载错误 | 检查 volume 与 root |
| 刷新页面白屏 | 缺少 history 回退 | 检查 try_files |
| 接口跨域 | 前端直连了后端地址 | 改为同域 /api 反代 |
| 连接超时 | 安全组未放通 80 | 检查入方向规则 |
八、绑定域名与 HTTPS
域名解析添加 A 记录指向 ECS 公网 IP,然后在华为云申请或上传 SSL 证书。Nginx 增加 443 配置,并把 80 跳转到 443:
server {
listen 80;
server_name example.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
server_name example.com;
ssl_certificate /etc/nginx/certs/fullchain.pem;
ssl_certificate_key /etc/nginx/certs/privkey.pem;
# 其余配置同上
}
证书文件同样以只读方式挂载进容器。改完配置执行:
docker compose exec web nginx -t
docker compose restart web
先 nginx -t 校验语法,再重启,能避免配置写错导致服务直接不可用。
九、几条实践建议
第一,镜像和依赖都锁定版本,方便回滚。第二,挂载配置和静态资源时加 :ro,减少误操作。第三,给容器日志设置大小上限,避免日志把磁盘写满:
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
第四,安全组遵循最小开放原则,只放通必要端口。第五,把构建、传输、重启写成脚本或接入 CI/CD,手动部署的次数越少,出错概率越低。
总结
这套方案的核心只有三件事:Nginx 负责入口与静态资源,后端只在内网通信,Docker Compose 把两者编排在一起。先把单机部署跑通,再考虑负载均衡、多副本和自动化发布,路径会清晰很多。
如果这篇文章帮你少踩一个坑,欢迎在评论区交流你的部署经历。
- 点赞
- 收藏
- 关注作者
评论(0)