Docker+nginx反向代理实战:从安装到容器化部署完整指南
2026/9/20 11:13:19 网站建设 项目流程

搞 Web 开发或者运维的同学,肯定绕不开两样东西:Docker 和 nginx。这两个单拎出来都不简单,但把它们凑在一起做反向代理,反而是日常开发里最常用、性价比最高的组合。今天这篇文章我就从一个实际落地的角度,带你把“安装 Docker”到“用 nginx 容器做反向代理”这条链路完整走一遍,过程中所有命令我都会解释为什么要这么写,不搞“复制粘贴跑通就算完”那套。

先说清楚这篇文章适合谁:想入门 Docker 的开发者、刚开始折腾服务器部署的运维新人、以及那些被“容器化部署”绕晕的团队新人,都可以照着这篇文章一步步操作。就算你之前完全没碰过 Docker,只要能看懂基础命令行,跟着走完就能理解容器、镜像、端口映射、挂载目录、反向代理这几个核心概念到底是怎么串起来的。

1. 先搞清楚:反向代理到底解决什么问题

1.1 为什么你的项目需要一个“统一入口”

咱们先不聊技术,聊一个特别常见的场景。你本地起了好几个服务:前端开发服务器在 5173 端口,后端接口在 8080 端口,可能还有个管理后台在 8888 端口。现在你要给同事演示,或者部署到服务器上,总不能让人家记住一串端口号吧?

更麻烦的是,如果以后服务多了,HTTPS 证书、负载均衡、日志记录、请求限制这些需求都会冒出来。你总不能在每个服务里都重复实现一遍这些东西。

反向代理解决的就是这个“统一入口”的问题。它把所有外部请求先接收到自己这里,再根据规则转发给后面的真实服务。对外,你只需要暴露一个 80 或 443 端口;对内,你想怎么转发都行。nginx 在这个领域是最老牌的工具之一,配置灵活、性能强,文档也丰富。

1.2 Docker 在这个场景里扮演什么角色

你可能要问:既然 nginx 可以安装到宿主机上,为什么还要用 Docker 再套一层?

我的答案是:为了可复制性和隔离性。

用 Docker 跑 nginx,你的宿主机不会因为装了一堆依赖而越来越乱。nginx 的配置、版本、运行环境全部被塞进一个镜像里,想换版本就换 tag,想回滚就用旧镜像重新起一个容器,全程不污染系统。换一台服务器时,你不需要重新踩一遍“编译安装 nginx”的坑,直接拉镜像起来就是一模一样的环境。

这个特性在团队协作里尤其香。你提交一个 docker-compose.yml 文件,同事一条命令就能把整套代理环境拉起来,再也不用对着文档手动配半天。

2. 把 Docker 环境装好,这是所有操作的前提

2.1 Windows / macOS 用户:直接上 Docker Desktop

Windows 和 macOS 没有太好的原生容器方案,Docker Desktop 是官方推荐的工具,它内部帮你管好了虚拟机、容器运行时、命令行工具这些杂事,安装完就能用。

Docker Desktop 安装其实没有太多花活,官网下载安装包,一路点下一步就行。但有两个前置条件容易踩坑:

  • Windows 需要开启 WSL2,并且系统要求是 Win10 64 位以上版本。
  • 如果你的机器 BIOS 里没开虚拟化,Docker Desktop 启动时会直接报错。

我第一次在 Windows 上装 Docker Desktop 时就卡在 WSL2 上,一直提示“Please enable WSL2”,最后是跑到“控制面板 -> 程序 -> 启用或关闭 Windows 功能”里勾选“适用于 Linux 的 Windows 子系统”和“虚拟机平台”,重启电脑才搞定。macOS 这边简单一些,Intel 芯片和 Apple Silicon 芯片下载的安装包不同,别下错了。

安装完成后,打开终端跑一句:

docker --version docker compose version

能输出版本号就说明基本环境没问题了。

2.2 Linux 用户:别用 Desktop,直接用 Engine

Linux 上没有必要装 Docker Desktop,装 Docker Engine 就够了。Ubuntu / Debian 系可以用官方安装脚本一步到位:

curl -fsSL https://get.docker.com | bash

国内网络环境下官方脚本有时拉取慢,可以改用国内镜像源手动安装,原理就是把官方源替换成清华或阿里的源,然后apt install docker-ce。CentOS / RHEL 系则是yum install docker-ce,流程类似。

装完以后有个必做动作:把当前用户加进 docker 组。否则每次执行 docker 命令都得带sudo,特别烦。

sudo usermod -aG docker $USER newgrp docker

这一步完成后再执行docker ps,如果不用 sudo 也能正常运行,环境就彻底通了。

2.3 镜像拉取慢?先配好镜像加速

很多人第一次用 Docker 都会遇到同一个问题:docker pull nginx卡了半天不动,进度条纹丝不动。这不是你网络坏了,而是 Docker 默认从 Docker Hub 拉镜像,从某些地区访问 Docker Hub 确实非常慢。

解决办法是配置“镜像加速器”。国内各个云厂商都提供了免费的 Registry Mirror,原理是在你拉镜像时自动从离你更近的镜像仓库同步。配置位置在 Docker Desktop 的设置里,或者 Linux 下的/etc/docker/daemon.json

{ "registry-mirrors": [ "https://docker.m.daocloud.io", "https://docker.xuanyuan.me" ] }

改完以后重启 Docker 服务,Linux 下用systemctl restart docker,Windows / macOS 直接在 Docker Desktop 里重启即可。之后再拉 nginx 镜像,速度会快非常多。需要提醒一句:镜像加速器地址经常变化,如果发现某个地址失效了,去搜一下当前可用的公共加速地址替换即可。

3. 用 Docker 跑起第一个 nginx 容器

3.1 从拉镜像到访问页面,只需要三条命令

环境就绪之后,我们来跑第一个 nginx 容器。整个操作只需要三步:

docker pull nginx

这条命令会从镜像仓库拉取 nginx 最新版镜像。如果上一步你配置好了加速,这里应该几十秒就能完成。

docker run --name web -p 80:80 -d nginx

这条命令的参数我逐个拆解一下:

  • --name web:给容器起个名字,后面管理容器时直接用名字,不需要记一串容器 ID。
  • -p 80:80:端口映射。冒号左边是宿主机端口,右边是容器内部端口。含义是“宿主机上的 80 端口收到的请求,转发到容器内部的 80 端口”。为什么容器内部是 80?因为 nginx 镜像默认监听 80 端口。
  • -d:后台运行,终端不卡住。

启动之后验证一下:

curl http://localhost:80

正常情况下你会看到一串 HTML,里面写着 “Welcome to nginx!” 这说明容器已经成功跑起来了。如果你看到的是这个页面,你的 Docker 入门第一步已经完成了。

3.2 为什么端口映射和目录挂载是 Docker 的核心操作

容器本身是一个“隔离的环境”,外面的世界访问不到容器内部,容器也访问不到外面,除非你主动开了一扇门。端口映射-p就是那扇门。

再看目录挂载。默认的 nginx 页面内容在容器里的/usr/share/nginx/html目录。你想部署自己的网站,总不能每次跑容器都用docker exec进到容器里改文件吧?那是纯折腾自己。

正确做法是把宿主机的目录挂载进去,用-v参数:

docker run --name web \ -p 80:80 \ -v /home/user/www:/usr/share/nginx/html \ -v /home/user/nginx/conf:/etc/nginx/conf.d \ -d nginx

这条命令的意思是把宿主机的/home/user/www目录映射到容器里的网站根目录,把本机/home/user/nginx/conf目录映射到 nginx 的配置目录。这样你改本机文件,容器里的内容就同步变了。做开发的应该能马上理解这个好处,改完代码不用重新打镜像、不用重建容器。

这里我要重点提醒一个坑:目录挂载的权限问题。如果宿主机目录的属主和属组跟容器内 nginx 进程的用户不一致,很可能会出现 403 Forbidden。我遇到过很多次,挂载目录后打开页面是 403,进容器一查,是宿主机目录权限是 755 但属主是 root,而 nginx worker 进程用的是 nginx 用户,读不了文件。解决办法是把宿主机目录权限改成 755 且允许其他用户读取,或者用--user参数指定容器运行用户,再或者干脆把目录属主改成 uid 101(nginx 官方镜像默认用户的 uid)。

3.3 nginx 配置文件在容器里应该怎么管理

nginx 官方镜像内部的目录结构跟宿主机编译安装的 nginx 基本一致,主配置文件在/etc/nginx/nginx.conf,站点配置在/etc/nginx/conf.d/目录下。默认的 nginx.conf 已经写好了 include 逻辑,会把/etc/nginx/conf.d/*.conf下的文件全部加载进来。

所以日常管理配置,最推荐的做法是:新增一个default.conf放到挂载的conf目录下,不需要去动主配置文件。

修改完配置,要让 nginx 重新加载配置,有两个层面:

  • 容器层面:docker exec web nginx -s reload,让容器里的 nginx 重新读配置。
  • 如果你的 nginx 配置改了容器端口或 conf 加载路径,那必须重建容器。

注意,我见过很多新人改完配置后不 reload,甚至直接docker restart web,这也不是不行,但 restart 会中断服务,而 reload 是平滑的,生产环境推荐用 reload。

4. 实战:配置 nginx 反向代理

4.1 一个典型场景:后端服务在 8080,通过 nginx 暴露到 80

现在进入正题,配置反向代理。我先给一个最经典的场景:你的后端服务跑在宿主机的 8080 端口,现在你想让用户通过 nginx 的 80 端口访问,不需要用户记http://ip:8080这种带端口的地址。

先准备一个配置文件/home/user/nginx/conf/default.conf

server { listen 80; server_name _; location / { proxy_pass http://127.0.0.1:8080; 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; } }

然后重建容器,把 conf 目录挂载进去:

docker run --name web \ -p 80:80 \ -v /home/user/nginx/conf:/etc/nginx/conf.d \ -d nginx

访问http://localhost,你会看到后端服务返回的内容。

这里最关键的一个知识点是proxy_pass后面的地址。我在 Linux 上用127.0.0.1:8080能通吗?答案是:在 Linux 上不一定通!这又是个经典坑。因为容器有自己的网络命名空间,容器里的127.0.0.1指的是容器自己,不是宿主机。要在容器里访问宿主机的服务,需要区分操作系统:

  • Windows / macOS(Docker Desktop):用http://host.docker.internal:8080,Docker Desktop 会自动把host.docker.internal解析到宿主机。
  • Linux:需要先查宿主机在 Docker 网络里的 IP,一般是172.17.0.1,或者用--network host运行容器,让容器直接使用宿主机网络栈,此时127.0.0.1就是宿主机。

我个人的建议是,在 Linux 上做这种代理到宿主机服务的场景,直接用--network host启动 nginx 容器,一了百了,不用纠结 IP。代价是端口映射参数-p就不需要了,因为容器直接共享宿主机网络。

4.2 代理到 Docker 网络内的另一个容器

更常见的场景是,不只 nginx 跑在 Docker 里,你的后端服务也跑在 Docker 里。这时候比宿主机 IP 更优雅的方案是让容器之间通过“网络”互相通信。

先创建一个自定义网络:

docker network create webnet

然后启动两个容器,都加入到这个网络:

docker run --name backend --network webnet -d backend-image docker run --name web \ --network webnet \ -p 80:80 \ -v /home/user/nginx/conf:/etc/nginx/conf.d \ -d nginx

这时候在 nginx 容器里,可以用backend这个容器名直接访问后端服务。配置改成:

location / { proxy_pass http://backend:8080; ... }

为什么能直接用容器名当域名?因为 Docker 内置了 DNS 解析,在同一个自定义网络里,容器名会被自动解析成对应容器的 IP。这就是容器编排里服务发现的基础机制。

对比一下,--link是老版本 Docker 的临时方案,现在基本可以不用了。自定义网络不仅支持容器名解析,还支持网络隔离,体验好太多。

4.3 多站点场景:按域名分流

真实项目里不可能只有一个服务。你可能有个前端站点、一个后端 API、一个管理后台。用 nginx 做反向代理,最常见的方式是按域名分流。

假设你有三个域名:

  • www.example.com对应前端,静态文件直接用 nginx 返回。
  • api.example.com对应后端 API,转发到 backend 容器。
  • admin.example.com对应管理后台,转发到 admin 容器。

配置文件可以这样写:

server { listen 80; server_name www.example.com; root /usr/share/nginx/html; index index.html; } server { listen 80; server_name api.example.com; location / { proxy_pass http://backend:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } } server { listen 80; server_name admin.example.com; location / { proxy_pass http://admin:3000; proxy_set_header Host $host; } }

每个server块根据server_name匹配不同的域名。DNS 解析需要把这三个域名都指向你的服务器公网 IP,nginx 收到请求后根据 Host 头决定转发给谁。

4.4 按路径转发:一个域名管多个服务

不是所有人都有多个域名。如果你只有一个域名,也可以通过路径区分服务:

server { listen 80; server_name example.com; location /api/ { proxy_pass http://backend:8080/; } location /admin/ { proxy_pass http://admin:3000/; } location / { root /usr/share/nginx/html; index index.html; } }

这里有一个细节容易踩坑:proxy_pass后面的 URL 是否带结尾的/,会影响转发时的路径拼接方式。

  • proxy_pass http://backend:8080;:请求/api/user转发的完整路径不变。
  • proxy_pass http://backend:8080/;location /api/中匹配到的/api/部分会被替换成/,所以/api/user变成/user再转发过去。

后端如果定义的路由是/user,那第二种写法才是对的。搞反了就会出现 404,而且你一时半会儿还找不到原因。

5. 引入 Docker Compose:让编排更简单

5.1 为什么长命令撑不住复杂场景

前面我们用了docker run启动 nginx 容器。单独一个服务还好,但当你需要同时启动 nginx、backend、admin、数据库,每个容器都要配端口、挂目录、加网络,命令会越来越长,而且不好维护。

Docker Compose 就是来解决这个问题的。它用一个docker-compose.yml文件描述所有服务,一条命令启动,一条命令停止。

5.2 一个完整的 compose 文件示例

假设我们有两个服务:nginx 和 backend。目录结构如下:

project/ ├── docker-compose.yml ├── nginx/ │ └── default.conf └── backend/ └── Dockerfile

docker-compose.yml内容:

version: '3.8' services: backend: build: ./backend container_name: backend networks: - webnet environment: - NODE_ENV=production nginx: image: nginx:1.27 container_name: web ports: - "80:80" volumes: - ./nginx/default.conf:/etc/nginx/conf.d/default.conf - ~/www:/usr/share/nginx/html depends_on: - backend networks: - webnet networks: webnet: driver: bridge

几个点说一下:

  • ports代替了命令行里的-p
  • volumes代替了-v,而且路径是相对于 compose 文件的相对路径。
  • networks定义了一个自定义网络,Compose 会自动把所有加入该网络的服务名解析到对应容器。
  • depends_on表示 nginx 依赖 backend,启动时先启动 backend。不过注意,depends_on只保证启动顺序,不保证 backend 内部服务已经 ready。生产环境需要用 healthcheck 配合。

启动命令就一句:

docker compose up -d

查看日志、停止服务、彻底删除服务:

docker compose logs -f nginx docker compose stop docker compose down

down会删除容器和网络,但不会删除挂载的数据卷,所以不用担心数据丢失。

5.3 为什么我建议你从第一天就上 Compose

很多教程喜欢从docker run教起,但我自己的体会是:只要你的项目有两个以上容器,或者你的 nginx 配置要跟项目一起版本管理,直接用 Compose 会让你少走很多弯路。

原因很朴素:docker run命令里的所有参数都是临时的,你不可能把它提交到 Git。而docker-compose.yml是代码,可以放进仓库,新同事 clone 下来直接up -d就能获得一模一样的环境。这不正是我们用 Docker 的初衷吗?

6. 常见问题与排查技巧实录

6.1 端口被占用:bind: address already in use

启动容器时最常遇到的就是端口冲突。现象是docker run报错,提示bind: address already in use

排查方法:

lsof -i :80 netstat -tlnp | grep :80

找到占用 80 端口的进程,要么停掉它,要么换 nginx 的宿主端口。如果你本机已经装了 Apache,它默认占用 80,大概率就是它跟 nginx 打起来了。

还有一个小技巧:如果端口被另一个 Docker 容器占用,用docker ps -a看一下,找到旧容器删掉就行。

6.2 502 Bad Gateway:上游服务不通

504 和 502 是反向代理时代最常见的错误。

502 Bad Gateway 表示 nginx 转发请求后,上游服务没响应或连不上。排查顺序:

  1. 看 nginx 配置里的proxy_pass地址对不对。
  2. 确认上游服务容器有没有启动:docker ps
  3. 在 nginx 容器里手动 curl 一下上游地址:docker exec web curl http://backend:8080
  4. 确认网络对不对:两个容器要在一个自定义网络里才能用服务名解析。

我之前帮同事排查过一个案例,nginx 配置完全正确,但一直 502。最后发现是 backend 容器启动后马上退出了,因为启动命令少了参数,进程根本没起来。docker ps -a能清楚地看到容器的退出状态码和启动日志,排查容器问题第一个动作永远是先看日志。

6.3 404 / 403:挂载目录的权限和路径坑

403 的问题之前提到了,通常是权限。404 则是路径配置问题。

常见场景:你挂载了宿主机目录,打开页面提示 403,那是因为容器内 nginx 用户没有宿主机目录的读取权限。对于 CentOS / Ubuntu 服务器场景,很多人的家目录默认权限是 700,容器里 nginx 用户自然是没法访问的。解决办法:把网站目录放到权限更开放的位置,或者用chmod -R 755调整目录权限。

404 多半是root路径写错了。注意 nginx 的root是“把 URL 拼接到 root 后面找文件”,如果你把网站文件放在/usr/share/nginx/html/index.html,root 就应该是/usr/share/nginx/html,别写成/usr/share/nginx,否则会去/usr/share/nginx/index.html找,自然 404。

6.4 配置修改后不生效

改了挂载目录里的default.conf,刷新页面没反应。99% 的情况是没有 reload。

正确的姿势:

docker exec web nginx -t docker exec web nginx -s reload

先执行nginx -t检查语法,语法错误的话 reload 会失败,而且你还不容易发现。这个习惯一定要养成,生产环境上直接 reload 一个语法错误的配置,后果是 nginx 直接拒绝加载配置,服务可能中断。

6.5 镜像拉取失败或一直拉不下来

除了配置加速器,还有几个进阶手段:

  • 换 tag:docker pull nginx:alpinedocker pull nginx体积小很多,也能避免某些大镜像因为分层多而失败。
  • 如果公司有 Harbor 或 Nexus 私有仓库,让运维把基础镜像同步到内网仓库,拉取速度最快。
  • 本地构建时尽量使用带 alpine 后缀的镜像,体积小、拉取快、漏洞也少。

我自己个人偏好就是:所有能用 alpine 的镜像全部选 alpine,不仅拉取快,磁盘占用也小,对服务器来说省下的是实打实的空间。

最后再分享一点我的实际感受

Docker 和 nginx 这套组合,真正难的不是某个命令怎么写,而是理解“容器是隔离的”、“网络是可自定义的”、“配置是可以通过挂载和版本管理进项目里的”这几个观念。一旦你把 Docker 的镜像、容器、数据卷、网络这四个核心概念想透了,后续不管是玩 Kubernetes 还是做 CI/CD,都会顺利很多。

这篇文章里我刻意绕开了很多进阶话题,比如 HTTPS 证书、负载均衡、WebSocket 支持、日志轮转,但你在掌握了基础之后完全可以自己去扩展。在我的实际使用中,nginx 反向代理 + Docker 的这套方案,无论放在本地开发还是生产环境都是非常稳的组合。建议你找一台测试服务器,亲手敲一遍这里的命令,遇到 502、403、404 不要慌,按上面提到的排查路径一步步来,这些坑踩过一次,后面就再也不会犯了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询