资讯动态

Docker 与 Nginx 反向代理 Node.js 服务实战指南

发布时间:2026/9/20 8:29:19 来源:尧图企业网站定制
本地开发的时候node app.js一把梭谁都会。等到了要部署、要给同事演示、要挂到服务器上的时候问题才会接踵而至3000 端口对外开放到底合不合适Node 进程崩了谁来拉起静态资源、接口转发、域名绑定这些事总不能全部塞进 Express 路由里硬扛。所以这篇文章聊的是一套非常经典、也极其实用的组合用 Docker 容器化一个 Node.js 服务再用 Nginx 作为反向代理把所有流量统一收口到 80 端口。整套流程通下来你会对容器、端口映射、服务编排和 Nginx 的核心转发逻辑有直观的理解。零基础可以跟着走有一点基础的人在排错部分也能找到对你有用的东西。我用一个最简单的场景作为主线本地写一个返回 JSON 的 Node.js 小应用然后用 Docker 把它跑起来再用 Nginx 反代到宿主机 80 端口。看起来简单但里面牵扯到的几个问题——镜像选型、Dockerfile 编写、容器间通信、代理配置、故障排查——全是日常开发里躲不开的。1. 为什么一套部署方案里要同时出现 Nginx 和 Node.js1.1 Nginx 在架构里到底扮演什么角色反向代理这四个字很多人听过但没细想过。我习惯用一个前台类比来解释你进一栋写字楼不需要知道 CEO 坐在哪个房间、财务在哪一层走到前台报一声要找谁前台帮你转接。客户端请求也是一样它访问你的域名或服务器 IP真正处理业务的是 Node.js 服务但客户端完全不需要知道 Node 跑在哪个端口。Nginx 监听在 80HTTP和 443HTTPS端口收到请求后按照配置规则把请求转发给内部网络中的 Node.js 服务。整个过程对客户端透明它只看到 Nginx感知不到背后还有一层 Node。这就是反向代理和正向代理的区别正向代理是替客户端访问外部资源比如你在浏览器里配置代理去访问某些网站反向代理是替服务端接收流量把请求分发到内部的真实服务上。为什么不能让 Node.js 直接监听 80 端口技术上当然可以用 root 权限启动或者设置端口转发就能做到但这么干问题不少。同一台服务器上如果还跑着一个前端项目、一个 WordPress 博客、一个管理后台它们都想占用 80/443 端口就会互相打架。Nginx 恰恰就是那个仲裁者通过server_name域名和location路径的搭配把不同请求分流到不同服务。1.2 Nginx 有哪些事比 Node.js 做得更好Node.js 是业务层选手擅长处理 IO 密集型任务、操作数据库、组织接口逻辑但让 Node 直接面向公网流量其实有点勉强。主要体现在几个方面静态文件服务Nginx 处理静态文件的效率极高几行配置就能开启缓存Node 当然也能返回静态文件但每个请求都要过一次业务代码浪费性能。连接管理Nginx 对海量并发连接的承受能力很强自带连接池、缓冲区和超时控制Node 的 HTTP server 虽然也不差但暴露到公网后还需要额外处理请求体大小限制、连接超时、慢速攻击等问题。负载均衡当 Node 服务需要横向扩展成多个实例时Nginx 的upstream模块可以很轻松地做请求分发Node 本身要自己做负载均衡就比较繁琐。HTTPS 终结证书配置在 Nginx 这一层内部服务之间走 HTTP 即可大幅简化 Node 证书管理的复杂度。简单说Nginx 是流量入口的守门员Node.js 是球场上的核心球员。两者配合各司其职这套架构能支撑的规模远超过单机单进程的 Node 应用。1.3 Docker 在这个架构里的价值Docker 解决的问题最核心的就是四个字环境一致。你在自己电脑上跑得好好的换到服务器上可能因为 Node 版本不同、系统依赖缺失、端口被占等各种原因跑不起来。用 Docker 把 Node 服务打包成镜像之后这个镜像里已经包含了运行所需的全部环境任何一台装了 Docker 的机器上跑起来行为都是一样的。而且 Docker 的隔离性天然适合这种前后端分离的架构。Node 容器里的 3000 端口和宿主机没有关系Nginx 容器里的 80 端口也可以按需映射。平时你在项目里还会用到 MySQL、Redis 这些中间件一个docker-compose.yml就能全部编排起来一键启动。你会发现第一次把整套环境用 Docker 串起来之后后面新加中间件、新加服务思路都是一模一样的。2. 动手前先准备Docker 安装、镜像加速与基础镜像挑选2.1 先跑通 docker version 这一关不论你用的是 Windows、macOS 还是 Linux第一步都是把 Docker 本体装好。Windows 和 macOS 用户直接装 Docker Desktop安装包从官网下载双击安装一路默认即可。Linux 用户稍微折腾一点Ubuntu 和 Debian 系一般用官方脚本curl -fsSL https://get.docker.com | bash - sudo systemctl enable --now docker装完之后打开终端验证一下docker version能看到 Client 和 Server 两段信息说明 Docker 守护进程已经在运行了。如果docker version里的 Server 段报错大概率是 Docker Desktop 没启动或者 Linux 下当前用户不在docker用户组里。Linux 下可以用sudo usermod -aG docker $USER加组然后重新登录终端。这一步卡住的人非常多我见过不少同学装了 Docker Desktop 之后忘了启动直接跑docker ps报一堆连接错误。先解决这个基础问题再往下走。2.2 镜像下载慢怎么破配置 registry mirror第一次拉镜像的时候很多人会被 镜像下载慢 劝退。这不是 Docker 本身的问题而是默认的 Docker Hub 官方源在国外国内访问经常很慢一个几十 MB 的 nginx 镜像能拉半天。解决方案是配置镜像加速地址。Docker Desktop 用户打开设置界面找到 Docker Engine 选项在 JSON 配置里加一段registry-mirrors。Linux 用户则需要修改/etc/docker/daemon.json然后重启 Docker{ registry-mirrors: [ https://your-registry-mirror.example.com ] }这个地址不需要自己建国内各大云厂商都提供免费的容器镜像加速服务去控制台申请一个专属地址填进去就行。配置完重启 Docker再用docker pull nginx:alpine试试速度会有明显改善。2.3 Node 和 Nginx 的镜像怎么选Docker Hub 上的官方镜像很丰富但版本标签多得让人眼花。很多人上来就挑最新的用结果踩了一堆坑。我建议按这个思路选Node 镜像优先考虑node:20-alpine或当前的 LTS 版本的 alpine 标签。Alpine Linux 是个极简发行版体积只有几十 MB相比几百 MB 的标准版镜像小很多。缺点是一些依赖原生模块的 npm 包在 Alpine 上需要编译可能比你预想的多花点时间。如果遇到原生模块编译问题可以退回到node:20-slim体积也还可以兼容性更好。纯入门学 Docker不想折腾编译问题直接用node:20也行省心最重要。Nginx 镜像优先用nginx:alpine体积小功能不打折。反代配置和标准版没有区别。选镜像的原则是不盲目追求最新优先选 LTS 版本和 alpine 变体。基础镜像选对了后面构建速度和磁盘占用都会舒服很多。2.4 本机 Node 环境的小提醒虽然理论上有了 Docker 就不必在本机装 Node.js但开发时用本机跑跑调试还是方便不少。如果你打算顺便装一个本机 Node 环境我建议用 nvmNode Version Manager来管理版本不要直接从官网下载安装包因为后面前端项目多了Node 版本切换是常有的事。装完 nvm 之后安装 LTS 版本nvm install --lts nvm use --lts这里提醒一个常见的报错error installing 24.20.0: node.js v24.20.0 is not yet released or is not available。出现这个信息基本可以断定是 Node 版本号写错了或者该版本尚未发布。nvm 能安装的版本号以上游官方 release 列表为准如果你指定的版本号早于发布时间、写错了小版本号或者这是某个 nightly 版本都会报这个错。解决办法很简单先执行nvm ls-remote看看目前有哪些可用版本再从中挑一个 LTS 安装别自己臆造版本号。3. 先让 Node.js 在 Docker 里跑起来Dockerfile 实战3.1 最小 Node 项目长什么样在开始写 Dockerfile 之前先把这个小项目准备出来。我建一个app目录里面就两个文件。package.json{ name: node-docker-demo, version: 1.0.0, description: A simple Express server for Docker Nginx demo, main: server.js, scripts: { start: node server.js }, dependencies: { express: ^4.19.2 } }server.jsconst express require(express); const app express(); const port 3000; app.get(/, (req, res) { res.json({ message: Hello from Node.js inside Docker, service: node-app, }); }); app.get(/api/users, (req, res) { res.json([ { id: 1, name: Alice }, { id: 2, name: Bob }, ]); }); app.listen(port, () { console.log(Node app listening on port ${port}); });就这些。这是个无状态的小服务一个根路径、一个/api/users路径刚好用来验证后面 Nginx 的路径转发。3.2 Dockerfile 每一行都在解决什么问题在app目录下创建Dockerfile注意没有扩展名FROM node:20-alpine WORKDIR /app COPY package*.json ./ RUN npm install --registryhttps://registry.npmmirror.com COPY . . EXPOSE 3000 CMD [node, server.js]逐行拆开看FROM node:20-alpine指定基础镜像。这里选了 Node 20 的 Alpine 版本当前 Node.js 的 LTS 是 20 或 22具体看你手上的项目但思路一致。WORKDIR /app设置容器内的工作目录。后续的命令都会在这个目录下执行建议显式指定避免和容器里的系统目录混淆。COPY package*.json ./把package.json和package-lock.json如果有复制到镜像里。这里特意只复制依赖清单不复制整个项目是为了利用 Docker 的层缓存机制。RUN npm install --registryhttps://registry.npmmirror.com安装 npm 依赖。后面的--registry参数是给国内网络环境准备的使用 npmmirror 镜像源显著加快依赖安装速度。如果你网络环境不受限直接用npm install就行。COPY . .把当前目录下所有文件复制进镜像。放在npm install之后是因为依赖变更的频率远低于代码变更这样可以最大化利用缓存——只要package.json没变npm install就会命中缓存层。EXPOSE 3000声明容器内服务监听在 3000 端口。这只是文档性质的声明不会真的自动发布端口真正的端口映射在docker run或 compose 文件里做。CMD [node, server.js]启动命令。注意 JSON 数组格式这是 exec 形式直接执行node server.js不经过 shell。用 shell 形式CMD node server.js也能跑但 exec 形式更规范至少不会因为 shell 进程意外退出而丢日志。3.3 .dockerignore别把本地依赖塞进镜像这个文件容易被忽略但非常重要。如果你项目目录里有node_modules直接执行COPY . .就会把本地安装的一堆依赖也复制进容器体积立马膨胀而且不同平台的本地依赖进容器后很可能不可用。所以在app目录下再创建一个.dockerignorenode_modules .git .gitignore *.log .DS_Store这样构建镜像时Docker 会直接忽略这些目录和文件不给它俩进镜像的机会。写不写.dockerignore构建出来的镜像大小和构建速度差别很大养成习惯最好。3.4 构建、运行并验证在app目录下执行docker build -t node-docker-demo .-t给镜像起个名字方便后面引用。构建过程会分步骤输出日志注意看每一层的状态。第一次构建需要拉基础镜像可能慢一点第二次构建由于缓存命中速度会非常快。构建完成之后先把容器跑起来验证一下docker run -d --name node-app -p 3000:3000 node-docker-demo参数含义很直接-d后台运行--name给容器指定名字-p把宿主机的 3000 端口映射到容器的 3000 端口。然后看一眼容器的运行状态docker ps docker logs node-app浏览器访问http://localhost:3000能看到 JSON 返回说明 Node 容器已经正常工作了。这一步先不接触 Nginx确认基础服务没问题再往上叠加排查会省力很多。4. 写一份能直接抄的 Nginx 反向代理配置4.1 配置文件的组织方式Nginx 容器启动后默认的用户配置目录在/etc/nginx/。主配置文件是nginx.conf里面通过include /etc/nginx/conf.d/*.conf;把conf.d目录下所有.conf文件加载进来。因此我们不需要去改主配置只需要在宿主机上创建一个nginx/conf.d/default.conf文件然后通过 Volume 挂载到容器内的/etc/nginx/conf.d/目录即可。这样既干净又方便修改。4.2 一份最小可用的反向代理配置先创建目录结构mkdir -p nginx/conf.d然后编写nginx/conf.d/default.confserver { listen 80; server_name localhost; location / { proxy_pass http://node-app:3000; 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; } }这个配置不长但每一行都有讲究listen 80Nginx 监听容器内的 80 端口。server_name localhost匹配请求的 Host 头。如果访问的是本机 IP可以写服务器 IP如果绑定了域名就写域名。这里开发环境用 localhost 就够了。location /匹配所有路径的请求把请求交给proxy_pass指定的地址。proxy_pass http://node-app:3000这是核心。node-app是 Docker Compose 网络里的服务名Nginx 容器可以通过这个名字访问到 Node 容器后面详细说。四个proxy_set_header把客户端的真实信息透传给后端。为什么这些 header 很重要因为经过了 Nginx 这一层后端 Node.js 的req.ip如果直接用req.socket.remoteAddress看到的是 Nginx 容器的内网 IP而不是客户端的真实 IP。加了proxy_set_header X-Real-IP $remote_addr之后后端代码里读req.headers[x-real-ip]就能拿到真实 IP。类似地X-Forwarded-For和X-Forwarded-Proto分别记录了原始请求来源链路和原始协议HTTP/HTTPS这些都是应用层做日志分析、判断真实来源的常用依据宁可直接带上。4.3 最容易踩的坑proxy_pass 的路径拼接规则很多人在写反向代理时被绕晕的就是proxy_pass的路径替换规则。我直接举例说明。情况一proxy_pass不带 URI不带斜杠location /api/ { proxy_pass http://node-app:3000; }请求/api/users会被原样转发为http://node-app:3000/api/users后端 Express 的/api/users路由能正常匹配。情况二proxy_pass带 URI带了路径部分location /api/ { proxy_pass http://node-app:3000/; }请求/api/users会被替换为http://node-app:3000/users注意前缀/api被丢弃了。这是因为当proxy_pass后面带 URI 时Nginx 会用 location 匹配部分替换掉原请求的对应前缀。这个特性不区分是路径还是斜杠只要proxy_pass的地址里带了路径部分行为就会不同。实际开发中常见做法是在 Nginx 层去掉/api前缀然后后端只用/users这样的路径另一种做法是保持proxy_pass不带路径让后端接口继续带/api前缀。两种都行关键是前后端口径要一致不然就会出现前端调/api/users后端却收到/users或/api/users的情况接口 404 找不出原因。4.4 配置文件的检查与热更新改完default.conf不要直接重启容器先用 Nginx 自带命令检查语法docker run --rm -v $(pwd)/nginx/conf.d:/etc/nginx/conf.d:ro nginx:alpine nginx -t这个命令临时起一个容器把宿主机上的配置目录挂载进去然后执行nginx -t只做语法检查不真正运行。如果输出syntax is ok说明配置没问题。如果 Nginx 容器已经在运行重新载入配置不需要重启整个容器docker exec nginx nginx -s reloadnginx -s reload会平滑地重新加载配置几乎不中断现有连接这是 Nginx 一个非常好的特性。改配置、检查、热更新这三步是日常操作的高频组合。5. 用 Docker Compose 把 Node 和 Nginx 串成一个系统5.1 为什么我用 docker-compose 而不是两条 docker run到了这一步你已经有两个容器需要启动Node 服务和一个 Nginx。如果你用两条docker run分别启动还得手动创建一个网络让两个容器之间能通信。一旦项目里再加 MySQL、Redis命令会越来越长管理成本剧增。Docker Compose 就是来解决这个问题的。它用一个docker-compose.yml文件描述所有服务一条命令就行。5.2 docker-compose.yml 逐项解释在项目根目录app和nginx的上一级创建docker-compose.ymlversion: 3.8 services: node-app: build: ./app container_name: node-app restart: unless-stopped expose: - 3000 environment: - NODE_ENVproduction nginx: image: nginx:alpine container_name: nginx restart: unless-stopped ports: - 80:80 volumes: - ./nginx/conf.d:/etc/nginx/conf.d:ro depends_on: - node-app这里有几个关键点。node-app服务用的是build: ./app会读取app目录下的 Dockerfile 进行构建nginx服务直接用nginx:alpine镜像不需要本地 Dockerfile因为它本身不需要定制只是挂载配置。注意一个细节node-app只声明了expose: [3000]没有写ports映射。这意味着 Node 服务的 3000 端口只对 Docker 网络内的其他容器开放宿主机和外部网络完全访问不到。在后面的架构里外部流量只进 Nginx 的 80Node 服务没必要暴露到宿主机少开一个端口就少一份风险。这也是容器化架构里推荐的做法内部服务之间用容器网络通信只有入口服务才做端口映射。nginx服务的ports: [80:80]是把容器内 80 端口映射到宿主机 80 端口。volumes把宿主机上的nginx/conf.d目录挂载到容器内ro表示只读防止容器内意外修改宿主机文件。restart: unless-stopped是常用的重启策略只要不是手动 stop 掉的容器都会在退出后自动重启。这样能保证比如服务器重启后整套服务自动拉起。depends_on表示启动顺序上 Nginx 依赖 Node先启动 node-app 再启动 nginx。当然它只保证启动顺序不保证 Node 服务内部已经完全就绪所以实际使用中如果生命周期很短的应用存在竞态还要搭配 healthcheck这个在后面进阶部分说。5.3 从零到完整的启动流程在项目根目录执行docker compose up -d --build-d后台运行--build在启动前重新构建镜像。这条命令会构建 Node 镜像、拉取 Nginx 镜像、创建网络并启动两个容器。然后验证docker compose ps curl http://localhost curl http://localhost/api/users如果现在访问http://localhost能拿到 JSON 数据说明 Nginx 到 Node 的整条链路已经通了。你还可以故意把server.js里的内容改一下重新docker compose up -d --build再观察变化体会一下镜像构建和容器重启的过程。这里有个有意思的验证方式在宿主机执行docker exec -it nginx sh进入 Nginx 容器然后curl http://node-app:3000你会发现从 Nginx 容器里能直接访问到node-app这个服务名。这种基于 Docker 内置 DNS 的服务发现机制正是 Compose 网络里容器间通信的基础。5.4 开发模式下如何让代码改动立即生效上面的流程每次改代码都要重新构建镜像开发效率很低。实际开发时更推荐把源码目录挂载进容器配合nodemon实现热更新。修改docker-compose.yml里 node-app 的部分增加一个 volumes 挂载node-app: build: ./app container_name: node-app restart: unless-stopped expose: - 3000 volumes: - ./app:/app environment: - NODE_ENVdevelopment同时把CMD改成用 nodemon 启动或者先在package.json里加上nodemon依赖和启动脚本scripts: { dev: nodemon server.js }Dockerfile 里最后一行改为CMD [npm, run, dev]现在宿主机app目录下的代码改动会实时同步到容器内nodemon 检测到文件变化后自动重启 Node 进程整个开发节奏就顺滑了。挂载卷和镜像构建是两套逻辑生产环境不挂载卷、使用构建进去的代码开发环境挂载卷、用本地代码覆盖容器内代码理解了这点就不会混淆。6. 从 502 到 403容器化架构的排错实录6.1 502 Bad Gateway 的标准排查链路反向代理架构里最常见的错误就是 502 Bad Gateway意思是 Nginx 在工作但它背后要访问的那个服务没有响应或根本不存在。我见过太多同学一看到 502 就懵了其实按顺序排查就能快速定位。第一步看容器状态docker compose ps确认node-app容器是不是处于 Up 状态。如果容器状态是 Exited 或者反复 Restarting大概率是 Node 进程启动失败进入下一步。第二步看 Node 日志docker compose logs node-app能看到Node app listening on port 3000说明启动成功如果看到类似EADDRINUSE之类的错误是端口被占用看到Cannot find module之类的报错则可能是 Dockerfile 里COPY漏了文件或者node_modules没装齐。第三步确认服务名和端口是否匹配。Nginx 配置里的proxy_pass http://node-app:3000这个node-app必须和docker-compose.yml里 Node 服务的名字一致端口必须和服务实际监听的端口一致。如果 Node 服务里监听的是process.env.PORT而你没有设置这个环境变量默认可能是 8080 或其他端口但 Nginx 还在往 3000 转发端口不一致必然 502。最后再考虑网络问题。如果两个服务在一个 compose 文件里默认会加入同一个网络通常不会有网络隔离问题但如果你之前手动建过网络、或者用了外部网络配置就要确认两个容器是否真的在同一个网段。执行docker inspect nginx看 Networks 部分再对比docker inspect node-app如果两个容器不在同一个网络互相访问自然失败。6.2 反向代理出现 403 的几种常见情况403 相对少一些但出现后比较迷惑。常见的几个原因Nginx 用户权限不足比如 Nginx 要读取挂载进来的静态文件目录但宿主机文件权限是 700容器内nginx用户没有读取权限。这种情况在挂载宿主机目录时经常发生给目录加上合适的权限或用chmod调整即可。上游服务返回 403Nginx 只是个传话筒后面 Node 接口本身对来源有校验返回 403Nginx 也会原样转发给客户端。查问题时要记得看后端日志不要只盯着 Nginx 配置。代理头影响某些应用会校验X-Forwarded-For头来判断来源如果 Nginx 没有透传这些头后端可能会拒绝请求。这也是为什么我在第 4 章反复强调 proxy_set_header 要写全。遇到 403先确认访问的是静态文件还是动态接口再分头查 Nginx 日志和上游日志。Nginx 的错误日志一般能给出很明确的原因。6.3 端口冲突80 被谁占了把端口映射到宿主机 80 的时候最烦的事情就是端口被占用。macOS 上常见的坑是 AirPlay 接收器占用了 5000 和 7000 端口但也有一些系统组件会占用 80Windows 上 IIS、SQL Server Reporting Services 默认可能占用 80Linux 上 Apache 或系统自带 Web 服务也会占 80。排查命令macOS/Linuxsudo lsof -i :80或sudo netstat -tulpn | grep :80Windowsnetstat -ano | findstr :80找到占用进程后如果确认可以停掉就停掉实在需要共存就把 Nginx 映射到其他端口比如8080:80访问时用http://localhost:8080。注意修改端口后Nginx 容器内部的listen 80不需要改动因为改的是宿主机到容器之间的映射关系而容器内 Nginx 的服务端口还是 80。6.4 改了代码或配置不生效的两个高频原因“我明明改了文件为什么访问没变化”这个问题平均每周都能遇到一次。通常就两种原因。第一种是代码改了镜像没重新构建。如果你没有挂载卷容器里跑的是构建时打进镜像的那份代码宿主机上的文件改动跟容器没有关系。解决方式就是重新构建并启动docker compose up -d --build第二种是 Nginx 配置改了没有 reload。Nginx 的配置是启动时加载的你改了default.conf但运行中的容器还在用旧配置。规范做法是改完配置先检查语法然后 reloaddocker compose exec nginx nginx -t docker compose exec nginx nginx -s reload6.5 日志怎么看才高效容器化之后日志的查看方式和本地不太一样。本地你可以盯着终端容器里要用docker logsdocker compose logs -f --tail 200 node-app-f持续追踪输出--tail 200只显示最近 200 行。Nginx 的日志也值得专门看docker compose logs -f nginxNginx 容器里默认的访问日志和错误日志分别在/var/log/nginx/access.log和/var/log/nginx/error.logdocker logs nginx会把 stdout 和 stderr 的输出显示出来。如果觉得不够直观可以在配置里加一行access_log /dev/stdout;让 Nginx 日志直接打到标准输出docker logs就能直接看到。7. 项目上线前值得做的三个进阶动作7.1 多阶段构建把镜像从几百 MB 降到几十 MB前面用的 Dockerfile 是很直观的单阶段构建对学习足够友好但生产环境里镜像偏大。多阶段构建的思路是先用一个包含完整编译工具链的基础镜像构建产物再把产物拷贝到一个更干净的运行镜像里中间用的临时镜像不会被保留。以 Node.js 为例如果你的项目需要编译原生模块或者前端构建生成 static 文件可以这样写# 第一阶段构建依赖和产物 FROM node:20-alpine AS builder WORKDIR /app COPY package*.json ./ RUN npm install --registryhttps://registry.npmmirror.com COPY . . # 这里执行构建比如 npm run build # 第二阶段运行镜像 FROM node:20-alpine WORKDIR /app ENV NODE_ENVproduction COPY --frombuilder /app/node_modules ./node_modules COPY --frombuilder /app/dist ./dist COPY server.js ./ EXPOSE 3000 CMD [node, server.js]最终镜像里只保留运行所需的依赖和产物不包含源码、编译工具和临时文件。镜像体积减少以后推送、拉取、启动速度都有明显提升。7.2 用 upstream 给 Node 做负载均衡当你需要水平扩展 Node 服务时Nginx 的upstream模块是内置的解决方案。它最基本的用法是轮询upstream node_cluster { server node-app-1:3000; server node-app-2:3000; } server { listen 80; location / { proxy_pass http://node_cluster; proxy_set_header Host $host; } }在docker-compose.yml里用docker compose up --scale node-app2就可以启动多个 Node 实例Nginx 会自动在它们之间分发请求。这个用法在本地模拟集群时非常直观以后上生产环境配合容器编排平台思路也是类似的。7.3 给容器加 healthcheck让编排更健壮裸的depends_on只保证启动顺序不保证服务就绪。更稳妥的方式是给服务加上健康检查让 Compose 在确认上游健康之后再启动依赖它的服务。Node 服务的健康检查可以这样加node-app: build: ./app expose: - 3000 healthcheck: test: [CMD, wget, -qO-, http://localhost:3000/] interval: 10s timeout: 3s retries: 3Nginx 那边再配合depends_on的条件写法depends_on: node-app: condition: service_healthy这样 Nginx 只会在 Node 真正能响应请求后才启动减少了启动阶段出现 502 的窗口期。实际项目里像数据库这类服务的健康检查尤其有价值因为数据库完全初始化完成可能要好几秒盲等会导致应用启动即连接失败。这套组合拳打完你手里已经有了一套能跑的 Docker Nginx Node.js 基础设施。我个人在多次实践里的体会是不要一上来就追新技术先把最小的链路跑通再逐步往里面加数据库、加缓存、加 HTTPS每一步都验证过再往前走。容器化最容易踩坑的地方不是命令记不住而是对“容器间通信”和“端口映射”这两件事的理解不到位。把这两个模型想透了后面玩转 K8s、Serverless 这些概念都会轻松很多。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价