Docker + Nginx + Node.js:从“我的电脑能跑,你的电脑跑不了”到一键部署

Docker + Nginx + Node.js:从"我的电脑能跑,你的电脑跑不了"到一键部署

读完这篇,你会彻底搞懂容器隔离、端口映射、反向代理这三个概念,并能亲手搭建一个可用的 Web 服务栈。


一、一个古老而扎心的故事

每个程序员都遇到过这样的场景:

"在我的电脑上明明跑得好好的,怎么到你电脑上就崩了?"

原因往往不是代码写错了,而是运行环境不一致------你装的是 Node 22,项目要求 Node 16;你用的 Redis 6,生产环境是 Redis 5;甚至连操作系统底层库都不同。

传统解决办法是:写一份长长的部署文档,让运维手动安装各种依赖,版本对不上就出 Bug。这种"人肉运维"既低效又容易出错。

Docker 就是来解决这个问题的。

Docker 是一个应用容器化工具 ,它的口号很直白:"Build, Ship, Run" (构建、运送、运行)。

就像海运集装箱一样,不管里面装的是电子产品还是水果,一旦装入标准集装箱,就能被任何一艘巨轮运走。

类比一下:

  • Docker = 应用 + 运行环境(打包成一个整体)
  • 集装箱 = 你的代码 + 它需要的所有系统库、运行时、依赖

只要这个"集装箱"被打包好,就可以在任何安装了 Docker 的机器上直接运行,无需再关心环境差异。


二、隔离是什么?为什么它这么重要?

Docker 的核心能力是隔离。但这到底隔离了什么?

我们用一个真实的例子说明:

你刚入职一家公司,接手一个五年前用 Vue 2 写的项目,它依赖 Node 16 和 npm 8。

可你电脑上装的是 Node 22------直接运行 npm install 就报错,因为某些老旧的依赖包在 Node 22 下无法编译。

传统的解决方法:用 nvm 切换 Node 版本,或者干脆装一个虚拟机。但这些方法要么侵入性太强,要么太重。

Docker 的隔离 ,并不是虚拟出一整台电脑,而是在操作系统层面为你的应用划定一个"独立小房间"。这个房间里有:

  • 自己独立的文件系统(包含 Node 16、npm 8、项目代码)
  • 自己独立的进程空间(看不到宿主机上的其他进程)
  • 自己独立的网络栈(有自己的 IP 和端口,与宿主机隔离)
  • 受限制的资源配额(CPU、内存上限)

这样,你的 Vue 2 项目就可以安安稳稳地住在这个"房间"里,哪怕宿主机上装了 Node 22,也完全不会冲突。


三、Docker 两大基本概念:Image 和 Container

  • Image(镜像) ------ 相当于一张光盘 。它是一个只读的模板,里面包含了应用程序 + 运行环境的所有文件。你可以把镜像推送到 Docker Hub 或私有仓库,像 git pull 一样拉取。

  • Container(容器) ------ 相当于把这张光盘放入 DVD 驱动器后运行起来的实例。容器是可写的、可运行的,你可以启动、停止、删除它。

类比:

Image 是类(Class),Container 是对象(Instance)。

一个 Image 可以同时运行出多个 Container,彼此完全隔离。


四、我们要搭建什么?

现在我们准备搭建一个最简单的 Web 服务栈:

  • 一个 Node.js 应用 ,监听 1314 端口,返回 "hello world"
  • 一个 Nginx 反向代理 ,监听 80 端口,把所有请求转发给 Node 应用的 1314 端口。

用户只需访问 http://localhost(即默认的 80 端口),就能看到 hello world

这样做的目的是:

  1. 统一入口:用户不需要知道后端具体跑在哪个端口,只需访问标准的 80 端口。
  2. 方便扩展:以后如果后端换成多个实例,Nginx 可以轻松做负载均衡。
  3. 安全加固:Nginx 可以处理 SSL 终止、限流、过滤恶意请求等。

五、编写一个极简的 Node.js HTTP 服务器

我们用 Node.js 内置的 http 模块创建一个服务器,监听 1314 端口,并在所有请求上返回 hello world

javascript 复制代码
// index.js ------ 使用 Node.js 原生 http 模块(CommonJS 风格)
const http = require('http');

// 创建 HTTP 服务器,收到任何请求都返回 "hello world"
const server = http.createServer((req, res) => {
  res.end("hello world");
});

// 监听 1314 端口,并绑定到 0.0.0.0(所有网络接口)
server.listen(1314, '0.0.0.0', () => {
  console.log(`Node server running on port 1314`);
});

关键点解析

  • 0.0.0.0 表示监听所有网络接口,这样容器内外 都能访问到这个端口。如果只写 localhost127.0.0.1,则只有容器内部能访问,外部(包括 Nginx 容器)无法连通。
  • 这个文件很简单,但足以代表任意一个后端服务(Java、Python、Go 同理)。

六、配置 Nginx 作为反向代理

Nginx 的配置文件 nginx.conf 如下:

nginx 复制代码
# nginx.conf ------ 极简反向代理配置
events {}  # 事件模型配置,这里留空使用默认值

http {
    server {
        listen 80;   # 监听容器内的 80 端口

        location / {
            # 将请求代理到后端地址
            proxy_pass http://host.docker.internal:1314;
            # 传递原始 Host 头,避免后端收到 localhost 而困惑
            proxy_set_header Host $host;
        }
    }
}

逐行解读(底层原理向)

  • events {}:这是 Nginx 处理并发连接的核心配置块。留空时使用默认的 epoll(Linux)或 kqueue(BSD)模型,足够应对大多数场景。
  • listen 80;:让 Nginx 监听容器内部的 80 端口。注意:这个端口是容器内的端口,与宿主机无关。
  • location /:匹配所有请求路径。
  • proxy_pass:这是反向代理的核心指令。它告诉 Nginx:"把收到的 HTTP 请求转发给这个地址"
    • 这里的 http://host.docker.internal:1314 是一个特殊的域名,仅在 Docker for Windows / Mac 中有效,它指向宿主机(即你的笔记本电脑)。因为我们的 Node.js 应用如果运行在宿主机上(而不是容器里),Nginx 容器需要能访问到宿主机的 1314 端口,就需要这个特殊 DNS。
    • 如果 Node 也运行在另一个容器中,这里应该写成 http://node-service-name:1314(使用 Docker Compose 的服务名)。
  • proxy_set_header Host $host;:默认情况下,Nginx 转发请求时会把 Host 头改成 localhost,这可能导致后端应用(尤其是根据域名做路由的)无法正确识别。$host 是 Nginx 内置变量,代表用户请求中的原始 Host 值,这样后端就能拿到真实的域名。

为什么不直接把 proxy_pass 写成 http://127.0.0.1:1314

因为 Nginx 运行在容器里,容器内的 127.0.0.1 指向容器自身 ,而不是宿主机。所以必须用 host.docker.internal 来指代宿主机 IP。


七、用 Docker 启动 Nginx 容器

现在我们要把 Nginx 跑起来,命令如下(来自你的笔记):

bash 复制代码
docker run \
  --name my-nginx-demo \                     # 给容器起个名字
  -p 80:80 \                                 # 宿主机 80 映射到容器 80
  -v C:\Users\Dai\OneDrive\桌面\workspace\djz-ai\backend\docker\demo\nginx.conf:/etc/nginx/nginx.conf \
  -d nginx                                   # 后台运行官方 nginx 镜像

参数深度解析

  • -p 80:80:端口映射。格式是 宿主机端口:容器端口
    • 用户访问 http://localhost:80 时,流量会被 Docker 转发到容器的 80 端口。
    • 如果容器内的服务监听的是 1314,但你想让用户通过 8080 访问,就可以写 -p 8080:80,然后访问 localhost:8080
  • -v ...挂载(Volume) ,把宿主机上的 nginx.conf 文件"覆盖"到容器内的 /etc/nginx/nginx.conf。这样我们无需重新构建镜像,只需修改本地配置文件,重启容器就能生效。
  • -d nginx-d 表示后台运行(detach),这样终端不会被阻塞。nginx 是官方镜像的名字,如果本地没有会自动从 Docker Hub 拉取。

八、整体架构与请求流程

整个系统由两个部分组成(假设 Node 运行在宿主机):

flowchart LR User[用户浏览器] -->|访问 localhost:80| Nginx[容器: Nginx 监听80] Nginx -->|反向代理: host.docker.internal:1314| Node[宿主机: Node.js 监听1314] Node -->|返回 hello world| Nginx Nginx -->|响应| User

用户只需知道 http://localhost,后续的路由、转发全部由 Nginx 处理。后端端口变化时,只需修改 Nginx 配置,用户无感知。


九、进阶思考:如果 Node 也放进容器呢?

在实际生产环境中,Node 也会被打包成镜像运行在容器里。此时 nginx.conf 里的 proxy_pass 就不能再写 host.docker.internal,而应该改成:

nginx 复制代码
proxy_pass http://node-app:1314;

并且使用 Docker Compose 将 Nginx 和 Node 放在同一个自定义网络中,让它们通过服务名互相访问。

一个简单的 docker-compose.yml 示例:

yaml 复制代码
version: '3'
services:
  node:
    build: .  # 使用当前目录的 Dockerfile 构建 Node 镜像
    ports:
      - "1314:1314"   # 可选,如果只想容器间通信,可以不用暴露到宿主机
  
  nginx:
    image: nginx
    ports:
      - "80:80"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf
    depends_on:
      - node   # 确保 node 先启动

此时 Nginx 容器内可以通过 node 这个主机名访问到 Node 容器。


十、隔离的本质到底是什么?

通过这次实践,我们可以更加具体地理解 Docker 所谓的"隔离":

  • 文件系统隔离 :Nginx 容器里有自己的 /etc/nginx,Node 容器有自己的 /usr/local/lib/node_modules,彼此互不干扰。
  • 网络隔离:每个容器有自己的虚拟网卡和 IP,端口空间独立------你可以在两个容器里都监听 80 端口而不冲突。
  • 进程隔离:容器内的进程 PID 从 1 开始,看不到宿主机或其他容器的进程,增强了安全性。
  • 资源限制 (未演示,但可用 --memory--cpus 设置):避免某个容器耗尽宿主机资源。

十一、总结

我们从"环境不一致"这个痛点出发,一步步完成了:

  1. 理解 Docker 的"集装箱"思想。
  2. 编写一个简单的 Node.js 服务。
  3. 配置 Nginx 作为反向代理。
  4. 使用 docker run 启动容器并挂载配置。
  5. 剖析端口映射、host.docker.internal 等底层细节。

你现在应该能回答以下问题了:

  • 为什么我的 Vue2 项目需要 Node 16,而 Docker 能轻松搞定?
  • Nginx 的 proxy_passproxy_set_header 分别做了什么?
  • -p 80:80 中的两个 80 分别代表什么?
  • 为什么容器内不能用 127.0.0.1 访问宿主机服务?

如果觉得有帮助,不妨自己动手敲一遍命令,体会一下"一次构建,到处运行"的爽快感。也欢迎在评论区留下你的疑问或踩坑经历,我们一起交流!

相关推荐
陆枫Larry33 分钟前
两步验证(2FA )到底是什么?
后端
JavaGuide37 分钟前
我用 Claude Code/ZCode+GLM-5.3 从零做了一款 Agent 游戏!
前端·后端
程序员清风1 小时前
专业再升级!程序员专属显示器明基RD280UG上手实测!
java·后端·面试
刘立军2 小时前
RESTful 与契约优先:规范接口定义,统一接口设计范式
后端·架构·ai编程
JavaGuide2 小时前
GitHub 4.5 万+ Star!GitNexus 把代码仓库变成了 Claude Code / Codex 能查询的知识图谱
后端·ai编程
子兮曰3 小时前
DeepSeek Harness 架构深潜:一个把 Agent 运行时做成纯插件树的开源 Harness
前端·后端·deepseek
子兮曰3 小时前
AI Agent 完整入门指南:从 LLM 到生产落地的 30+ 个核心概念
前端·后端·agent
渔夫正在掘金4 小时前
Cordis 插件热插拔能力深度解析
后端·node.js
不爱编程的小九九4 小时前
小九源码-springboot004-springboot智能阅读推荐系统
java·spring boot·后端