nginx 里写 localhost 反而 502?一条请求带你彻底搞懂 Docker 端口映射与反向代理

nginx 里写 localhost 反而 502?一条请求带你彻底搞懂 Docker 端口映射与反向代理

先看一段配置------这是一个 nginx 容器,用来反向代理本机 1314 端口上的 Node 服务:

nginx 复制代码
# nginx.conf
events {}
http {
    server {
        listen 80;
        location / {
            proxy_pass http://localhost:1314;   # ⚠️ 这里写了 localhost
            proxy_set_header Host $host;
        }
    }
}

启动它,浏览器打开 http://localhost,你看到的是:

复制代码
502 Bad Gateway

可你的 Node 服务明明在 1314 端口跑得好好的,curl http://localhost:1314 也通。为什么 nginx 一代理就 502?

我把这个坑调明白之后才发现:502 的根因不是配置格式写错,而是我根本没搞懂「一条请求在 Docker 里是怎么走的」。 这篇文章就把这条路走一遍------搞清楚之后,这种坑你以后一眼就能看出来。


一、Docker 到底解决了什么问题

Docker 解决的是「我电脑能跑,你电脑怎么跑?」的问题。

你接手一个三年前的 Vue2 项目,要求 Node 16 + npm 8,可你电脑装的是 Node 22------装回旧版本吧,别的项目又炸了。代码之外,一个应用其实还「依赖」一堆有版本要求的运行环境:Node、Redis、MySQL......

Docker 的思路是:把应用和它需要的运行环境打包成一个整体,在任何设备上都能原样跑起来。就像海运的集装箱------不管里面装的是红酒还是毛衣,港口都按「一个标准箱子」来处理。

Docker = 应用 + 运行环境(打包成一个标准集装箱)

这一句就够了。剩下两个概念是它的零件:

概念 比喻 是什么
镜像 image 光盘 只读模板:应用 + 环境,打包好的一整套
容器 container DVD 播放机 镜像运行起来之后的实例,可启动 / 停止 / 删除

同一个镜像(光盘)可以同时起多个容器(多台播放机放同一张盘),互不影响。

概念点到为止。真正的重头戏是:把 nginx 装进容器之后,一条请求是怎么从浏览器走到 Node 应用的?


二、一条请求的旅程:从浏览器到 Node

先交代我们这个 demo 的「地形」------注意,它刻意做了一个很常见的混搭:

  • Node 服务跑在宿主机上 ,监听 0.0.0.0:1314
  • nginx 跑在 Docker 容器里,监听 80
  • nginx 负责把 80 的请求,转发给宿主机的 1314

这种「一半在容器里、一半在容器外」的部署,恰恰是最容易踩 502 的。先把路线图看清楚:

text 复制代码
浏览器
  │  http://localhost   (默认就是 80 端口)
  ▼
宿主机 :80  ─────────────────────┐
                                 │ ① -p 80:80  端口映射(宿主 80 → 容器 80)
  ┌──────────────────────────────▼──────────────┐
  │           Docker 容器                        │
  │                                              │
  │   nginx :80   (listen 80)                  │
  │      │                                       │
  │      │ ② proxy_pass  反向代理                │
  │      ▼                                       │
  └──────┼───────────────────────────────────────┘
         │ ③ host.docker.internal:1314  从容器回到宿主机
         ▼
  宿主机 Node :1314  ──►  "hello world"

一张图,三个关键词,就是这篇文章的全部。下面一个一个拆。

-p 80:80:端口映射,谁映射谁?

启动 nginx 容器的命令长这样:

bash 复制代码
docker run --name my-nginx-demo \
  -p 80:80 \
  -v /path/to/demo/nginx.conf:/etc/nginx/nginx.conf \
  -d nginx

-p 80:80 的读法是 「宿主机端口 : 容器端口」------前面是外面,后面是里面:

text 复制代码
-p 80:80  =  宿主机 80  ──转发──▶  容器内 80
   ↑ 外面      ↑ 里面

为什么需要它? 因为容器有自己独立的网络,容器里的 80 端口默认在外面够不着。-p 就是开一扇门,把「宿主机的某个端口」和「容器的某个端口」对上。

记法:-p 冒号左边永远是你(宿主机),右边是容器。谁在外面谁在左。

-v:卷挂载,让容器用你的配置文件

-v 本机路径:容器路径 把宿主机的一个文件 / 目录「映射」进容器。

这里把本机的 nginx.conf 挂到容器里 nginx 默认读配置的位置 /etc/nginx/nginx.conf。这样你改本机的配置,容器里的 nginx 直接生效,不用每次重新 build 镜像。

-p-vdocker run 最高频的两个参数,记住冒号左边都是「你(宿主机)」就够了。

proxy_pass 反向代理,以及那个 502 的真相

现在到最关键的一步。nginx 收到 80 的请求后,proxy_pass 把它转发给谁?

nginx 复制代码
location / {
    proxy_pass http://host.docker.internal:1314;   # ✅ 正确
}

为什么写 host.docker.internal,而不是 localhost?这就是开头 502 的真相。

容器有自己的 localhost 容器是一个独立的小世界,它内部的 localhost 指向它自己------而容器里根本没有 1314 端口的服务(Node 在宿主机上呢)。所以 nginx 写 localhost:1314 时,是在「容器内部」找 1314,找不到 → 连接被拒 → 返回 502。

nginx 复制代码
proxy_pass http://localhost:1314;            # ❌ 在容器里找 1314,什么都没有 → 502
proxy_pass http://host.docker.internal:1314; # ✅ 穿过容器边界,回到宿主机找 1314

host.docker.internal 是 Docker 提供的一个特殊域名,专门用来让容器访问宿主机。它不是真实的 DNS 记录,而是 Docker 在容器里注入的一个别名,指向宿主机地址。

⚠️ 一个平台的坑:这个域名在 Docker Desktop(Mac / Windows)上开箱即用,Linux 上默认没有 ,得加 --add-host=host.docker.internal:host-gateway 才认。


三、为什么叫「反向」代理?和正向代理差在哪

你可能会想:nginx 不就是个转发吗,干嘛叫「反向」?关键在代理的是谁

正向代理 反向代理
代理对象 客户端(你的请求) 服务端(后端服务)
典型场景 公司 / 梯子代理服务器 nginx、网关
客户端是否知情 知道,要手动配代理 不知道,以为访问的就是服务
一句话 帮「你」上网 帮「服务器」接客
  • 正向代理:浏览器配一个代理服务器,请求先到代理,再由代理去访问真正的网站。代理站在「客户端」这边。
  • 反向代理 :客户端访问 http://localhost(80 端口),它以为这就是服务本身;实际上 nginx 在背后把请求转给了 1314 的 Node。客户端完全不知道后端在哪个端口、哪台机器。

「localhost 我们是不知道后端具体在哪个端口上运行的」------这就是反向代理的精髓。

为什么服务端要藏起来?因为这样后端可以随便换端口、加机器做负载均衡、加缓存、做 HTTPS 终结,而客户端无感知。80 是 HTTP 默认端口,用户不用记 :1314 这种尾巴。


四、动手复现:把 localhost 换成 host.docker.internal

光讲原理不算数,你复制这套代码跑一遍,能看到一模一样的结果。先准备一个最小 Node 服务:

javascript 复制代码
// index.js ------ 宿主机上的最小 HTTP 服务
const http = require('http');
const server = http.createServer((req, res) => {
    res.end('hello world');
});
// 🔑 关键:监听 0.0.0.0 而不是 127.0.0.1
// 0.0.0.0 会绑定所有网卡,容器才能通过 host.docker.internal 访问到它;
// 只监听 127.0.0.1 的话,请求会被挡在宿主机的回环地址上。
server.listen(1314, '0.0.0.0', () => {
    console.log('server is running on port 1314');
});

启动它,然后分别用两版 nginx 配置跑容器。

第一版:localhost(错)

nginx 复制代码
proxy_pass http://localhost:1314;
bash 复制代码
node index.js          # 宿主机 1314 跑起来
docker run --name nginx-bad -p 80:80 \
  -v /path/to/nginx-bad.conf:/etc/nginx/nginx.conf -d nginx
curl http://localhost

你会看到:

xml 复制代码
<html><head><title>502 Bad Gateway</title></head>...

docker logs nginx-bad 里的报错一针见血:

scss 复制代码
connect() failed (111: Connection refused) while connecting to upstream

Connection refused 说明 nginx 连 1314 都没连上 ------因为它在容器内部找 localhost:1314,那里什么都没有。

第二版:host.docker.internal(对)

nginx 复制代码
proxy_pass http://host.docker.internal:1314;
bash 复制代码
docker run --name nginx-good -p 80:80 \
  -v /path/to/nginx-good.conf:/etc/nginx/nginx.conf -d nginx
curl http://localhost
复制代码
hello world

两版配置只差一个词,结果一个是 502、一个是 200。这个对比就是这篇文章唯一想让你记住的东西。


五、几个 docker 命令收尾

顺带把 docker 运维常用的命令记一下:

bash 复制代码
docker pull nginx            # 拉取镜像(像 git pull)
docker run ... nginx         # 跑一个容器
docker stop $(docker ps -q)  # 停掉所有容器
docker rm $(docker ps -aq)   # 删掉所有容器
docker rmi nginx             # 删掉镜像

结尾

回到开头那个 502:nginx 写 localhost 会 502,不是配置格式错了,而是 localhost 在容器里指的是容器自己。 记住这张图就够了:

text 复制代码
浏览器 ──:80──▶ 宿主机 ──(-p 80:80)──▶ 容器内 nginx:80 ──(proxy_pass)──▶ host.docker.internal:1314 ──▶ Node

下次再遇到「容器里的东西访问不到宿主机」,第一反应就应该是:你写的 localhost,是不是其实指向了容器自己? 该用 host.docker.internal 的时候,别用 localhost

留一个问题给你:如果 Node 服务 装进另一个容器(而不是跑在宿主机上),proxy_pass 里该写什么?host.docker.internal 还对不对?欢迎在评论区聊聊------这是 Docker 网络最经典的下一课:容器之间怎么互相访问。


相关推荐
jimidou1 小时前
从 PM2、Supervisor 到 Docker:老项目部署现代化实战(以 HOJ 评测机为例)
docker
小林ixn1 小时前
Docker + Nginx 反向代理:从“我电脑能跑”到“哪里都能跑”
nginx·docker·容器
coder_lorraine1 小时前
手把手教你用 Docker Compose 部署 Twikoo 评论系统
mongodb·docker·容器
量化分析2 小时前
迁移docker部署的wordpress
运维·docker·容器
程序员cxuan2 小时前
一招教你在 Codex 中开启 1M 上下文
人工智能·后端·程序员
懒人82 小时前
8月18日更新 | 2026 最新可用 Docker 国内镜像源加速列表(Linux 环境下拉取镜像的方式进行演示)
docker
三言老师2 小时前
K8s集群运行时自动化运维全覆盖落地实操(上)
docker·容器·kubernetes
ly76893 小时前
负载均衡详解:从四层、七层到 LVS、Keepalived、Nginx 与 HAProxy
nginx·负载均衡·lvs
梅孔立3 小时前
Docker 部署 Python3\.11 Flask爬虫项目 终极稳定教程(解决线程报错/挂载失效/pip报错)
爬虫·docker·flask