前端转全栈:从 502 到 200,我的第一个 Docker+Nginx+Node.js 项目

前言

作为一名做了10年前端的开发者,我一直渴望突破"纯前端"的边界。这次,我决定用 Docker + Nginx + Node.js 搭建一个完整的全栈项目:前端静态页面通过 Nginx 托管,后端 API 通过 Nginx 反向代理,三个后端容器做负载均衡。本以为半天搞定,结果在 502 和 404 的泥潭里挣扎了两天......这篇文章记录了从"一脸懵"到"curl 200 OK"的全过程,希望能帮到同样在转型路上的你。

技术栈

  • 前端:纯 HTML/CSS/JS(单页应用)
  • 后端:Node.js + Express(返回当前时间)
  • 网关:Nginx(反向代理 + 负载均衡)
  • 容器化:Docker(自定义网络、端口映射、卷挂载)

项目结构

perl 复制代码
纯文本
my-nginx-config/
├── default.conf          # Nginx 反向代理配置
├── html/
│   └── index.html        # 前端静态页面
└── my-backend/
    ├── dockerfile        # Node.js 后端镜像构建文件
    ├── package.json      # Node.js 项目描述
    └── server.js         # Express 后端服务(返回当前时间)

第一步:搭建后端服务

创建 server.js

javascript 复制代码
javascript
const express = require('express');

const app = express();

const port = 3001;

// ✅ 添加 CORS 支持

app.use((req, res, next) => {

res.header('Access-Control-Allow-Origin', '*');

res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');

res.header('Access-Control-Allow-Headers', 'Content-Type');

next();

});

app.get('/time', (req, res) => {

const now = new Date();

res.send(now.toLocaleString());

});

app.listen(port, () => {

console.log(`Backend listening at http://localhost:${port}`);

});

构建并启动后端容器(这里假设你已经有 Node.js 镜像,或者用 node:18-alpine):

perl 复制代码
bash
# 构建镜像
docker build -t my-backend ./my-backend

# 启动三个后端实例(分别映射不同宿主机端口)
docker run -d --name backend1 --network fullstack-net -p 3001:3001 my-backend
docker run -d --name backend2 --network fullstack-net -p 3002:3001 my-backend
docker run -d --name backend3 --network fullstack-net -p 3003:3001 my-backend

注意:这里后端容器内部监听 3001 端口,宿主机映射到 3001/3002/3003。

第二步:编写 Nginx 配置文件

创建 default.conf

bash 复制代码
nginx
# 定义后端服务器组(负载均衡)
upstream backend_servers {
    server backend1:3001;
    server backend2:3001;
    server backend3:3001;
}

server {
    listen 80;
    server_name localhost;

    # 静态文件目录
    root /usr/share/nginx/html;
    index index.html;

    # 前端 SPA 路由兼容(防止刷新 404)
    location / {
        try_files $uri $uri/ /index.html;
    }

    # API 反向代理
    location /api {
        # 显式去掉 /api 前缀,避免双斜杠问题
        rewrite ^/api(/.*)$ $1 break;
        proxy_pass http://backend_servers;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

关键点rewrite 指令比 proxy_pass 末尾斜杠更可靠,它明确地将 /api/time 转换为 /time,避免了我在实践中遇到的"双斜杠"bug。

第三步:启动 Nginx 容器

javascript 复制代码
bash
docker run -d --name my-app-router \
    --network fullstack-net \
    -p 80:80 \
    -v 文件路径/my-nginx-config/default.conf:/etc/nginx/conf.d/default.conf \
    -v 文件路径/my-nginx-config/html:/usr/share/nginx/html \
    nginx:alpine

第四步:前端页面

创建 index.html

xml 复制代码
html
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Fullstack Demo</title>
    <style>
        body { font-family: Arial; text-align: center; margin-top: 100px; }
        h1 { color: #333; }
        #time { font-size: 24px; color: #007bff; margin-top: 20px; }
    </style>
</head>
<body>
    <h1>Hello Fullstack!</h1>
    <div id="time">Loading...</div>
    <script>
        fetch('/api/time')
            .then(res => res.text())
            .then(data => document.getElementById('time').innerText = data)
            .catch(err => document.getElementById('time').innerText = 'Error: ' + err);
    </script>
</body>
</html>

踩坑实录(精华部分)

坑1:容器启动后立即退出(Exited 1)

现象docker ps -a 显示 my-app-router 状态为 Exited (1)

原因 :Nginx 配置文件语法错误,比如 upstream 里写了不存在的容器名或端口。

解决 :用 docker logs my-app-router 查看错误日志,修正配置文件后重新运行容器。

坑2:502 Bad Gateway

现象 :浏览器访问 localhost 显示 502,后端容器正常运行。

原因:Nginx 无法连接到后端容器。常见原因:

  • 端口不匹配(后端监听 3001,但 Nginx 配置写成了 80)
  • 容器不在同一网络

排查方法

perl 复制代码
bash
# 1. 进入 Nginx 容器
docker exec -it my-app-router sh

# 2. 测试网络连通性
ping backend1

# 3. 测试后端服务
curl http://backend1:3001/time

坑3:404 Not Found(双斜杠陷阱)

现象 :Nginx 日志显示请求转发到了 //time,后端返回 404。

原因proxy_pass http://backend1:3001/; 末尾斜杠在某些情况下会产生双斜杠。如default.conf这样配置

解决 :改用 rewrite 显式处理路径,如上文所示。

坑4:前端跨域(CORS)

现象 :直接访问 localhost:3001/time 正常,但通过 Nginx 代理后报跨域错误。

原因 :前端代码直接请求了 http://localhost:3001/time,而不是通过 /api/time

解决 :修改前端 fetch 地址为相对路径 /api/time,让 Nginx 代理转发。

最终验证

  1. 确保所有容器正常运行:docker ps
  2. 在浏览器访问 http://localhost
  3. 看到 "Hello Fullstack!" 和当前时间,控制台无报错

收获与总结

全栈调试三板斧

  1. 后端自测curl http://localhost:3001/time(确认后端活着)
  2. 容器互测docker exec -it my-app-router shcurl http://backend1:3001/time(确认网络通)
  3. 模拟请求curl http://localhost/api/time(确认 Nginx 转发逻辑)

关键认知

  • Docker 网络让容器之间通过名字通信,宿主机通过端口映射访问。
  • Nginx 的 proxy_pass 末尾斜杠行为容易产生歧义,推荐用 rewrite 显式控制路径。
  • 日志是最好的老师:docker logsnginx -t 能解决大部分问题。

下一步计划

  • 接入 MySQL/MongoDB,实现数据持久化
  • 用 Docker Compose 一键编排所有服务
  • 部署到云服务器,让全世界都能访问

完整代码仓库

GitHub:github.com/qishuixian/...

相关推荐
jason_renyu2 小时前
Linux Docker 服务管理与排查实战指南(新手速查版)
linux·docker·新手docker学习·docker日志查询·docker服务管理
童槿顏丶2 小时前
Jenkins部署(Docker)
java·docker·jenkins
维核科技2 小时前
大模型私有化部署:Docker与Kubernetes实战避坑
docker·容器·kubernetes
呱呱巨基3 小时前
Docker 基础概念学习
linux·c++·学习·docker
七仔啊4 小时前
windows server 2022 安装 Linux Docker
linux·windows·docker
GlueNa2SiO34 小时前
07-Docker Compose多容器编排
笔记·学习·docker·容器
葵续浅笑5 小时前
K8s Pod内存居高不下假象排查与解决(JDK17 G1+Arthas实战)
java·容器·arthas
GlueNa2SiO35 小时前
01-Docker入门与核心概念
笔记·docker·容器