当前很多开发者在接手项目时,第一件事就是装 MySQL、Redis、Nginx。装完发现版本不对、环境冲突、配置路径找不到,换一台机器又要重新折腾一遍。而在线上环境,Nginx 还要承担反向代理、负载均衡、HTTPS 证书管理等职责。这套“容器化 + 反向代理”的组合拳,已经几乎是后端开发和运维的基本功了。
本文将围绕 Docker 与 Nginx 的完整组合使用,覆盖从 Docker 安装、Nginx 镜像部署,到反向代理、多站点域名配置、负载均衡,再到 Docker Compose 编排和常见报错排查,给出全套可复制的方案。无论你是刚接触容器的初学者,还是需要搭建开发环境的进阶开发者,都可以按本文一步步操作。
1. Docker 与 Nginx:为什么要组合起来用
1.1 Docker 解决了什么问题
Docker 是一种容器化技术。它可以把应用程序、运行环境、依赖库、配置文件全部打包成一个镜像,然后在任意安装了 Docker 的机器上以容器的形式运行。
没有 Docker 的时候,部署一个 Nginx 需要做这些事:
- 根据操作系统下载对应安装包;
- 手动处理各种依赖库;
- 修改系统服务配置;
- 维护多个环境的一致性。
有了 Docker 之后,一条 docker run 命令就能启动一个 Nginx 实例。不同项目的 Nginx 配置互相隔离,不会污染宿主机环境。需要升级版本时,只需要替换镜像并重新运行容器即可。
1.2 Nginx 在容器世界里扮演什么角色
Nginx 是一个高性能的 HTTP 服务器和反向代理服务器。在实际项目中,Nginx 通常是容器化架构里的“流量入口”,主要承担下面几类职责。
第一类是静态资源服务。前端构建产物(HTML、CSS、JS、图片)可以直接交给 Nginx 托管,性能好,配置也简单。
第二类是反向代理。后端服务通常运行在内网端口,不能直接暴露到公网。Nginx 监听 80/443 端口,将外部请求转发给后端容器或者服务。
第三类是负载均衡。当后端服务有多个实例时,Nginx 可以通过 upstream 分组,将请求分发到不同实例,提升整体吞吐量。
第四类是 SSL 证书管理。HTTPS 证书的加载和续期一般统一放在 Nginx 层处理,后端服务本身不感知证书。
1.3 Docker + Nginx 的典型应用场景
- 个人开发机一键启动前端项目,免去本机安装 Nginx 的麻烦;
- 开发环境模拟生产环境的反向代理和 HTTPS 配置;
- 服务器上使用 Docker Compose 同时管理 Nginx、后端服务、数据库;
- 多个 Web 站点共用一个服务器 IP,通过不同域名实现流量分发。
下面从环境准备开始,一步步搭建这套体系。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 环境准备:安装 Docker 并确认运行状态
2.1 Windows / macOS 安装 Docker Desktop
Windows 和 macOS 用户推荐使用 Docker Desktop,这是 Docker 官方提供的图形化管理工具。
安装时需要注意两个前置条件:
- Windows 需要启用 WSL2,并且 BIOS 中开启虚拟化;
- macOS 需要系统版本满足 Docker Desktop 的最低要求。
安装完成后,打开 Docker Desktop,等几分钟让 Docker Engine 启动。
如果启动时提示类似下面的报错:
code复制Docker Desktop failed to start because virtualisation support wasn't detected
可能性有很多,常见的有:
- BIOS 中 Intel VT-x 或 AMD-V 没有开启;
- Windows 虚拟机监控程序平台功能没有启用;
- Hyper-V 与第三方虚拟机软件冲突。
处理方式是重启进入 BIOS 开启虚拟化,然后在“控制面板 → 程序 → 启用或关闭 Windows 功能”中确认“适用于 Linux 的 Windows 子系统”和“虚拟机平台”两项已勾选。操作完成后重启电脑再启动 Docker Desktop。
2.2 Linux 安装 Docker Engine
Linux 下安装 Docker 通常使用官方仓库。以 Ubuntu / Debian 系列为例,常见安装命令如下。
bash复制# 更新 apt 软件包索引
sudo apt update
# 安装依赖工具
sudo apt install -y ca-certificates curl gnupg lsb-release
# 添加 Docker 官方 GPG 密钥
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg
# 添加 Docker 软件源
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
$(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
# 安装 Docker Engine
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
CentOS / RHEL 系列可以使用 yum 安装,步骤类似。无论使用哪个发行版,安装完成后都需要将当前用户加入 docker 组,避免每次执行 docker 命令都加 sudo。
bash复制sudo usermod -aG docker $USER
执行完这条命令后,需要重新登录终端或执行 newgrp docker 使用户组生效。
2.3 验证 Docker 是否可用
安装完成后,运行下面的命令验证。
bash复制docker version
docker info
如果输出中包含了 Client 和 Server 两部分信息,说明 Docker 已经正常运行。
再运行一个最小测试容器。
bash复制docker run hello-world
如果终端输出 “Hello from Docker!”,说明整个链路打通了。
2.4 拉取 Nginx 镜像
Nginx 官方镜像在 Docker Hub 上长期维护,拉取命令如下。
bash复制docker pull nginx:alpine
这里选择 alpine 标签而不是 latest,原因是 alpine 版本的镜像体积更小,适合日常开发和资源受限的服务器。如果需要在配置中用到更多系统工具,可以换成 nginx:stable 或指定版本号,例如 nginx:1.25。
拉取完成后,通过下面的命令确认本地镜像列表。
bash复制docker images
输出中应该能看到 nginx 和 alpine 相关的记录。
3. Docker 部署 Nginx:从第一步 run 开始
3.1 使用 docker run 快速启动
先看一个最简单的启动命令。
bash复制docker run --name my-nginx -d -p 8080:80 nginx:alpine
参数解释如下:
--name my-nginx:给容器命名为 my-nginx;-d:后台运行容器;-p 8080:80:将宿主机的 8080 端口映射到容器的 80 端口;nginx:alpine:指定使用的镜像。
启动后访问 http://localhost:8080,可以看到 Nginx 默认页面。
这是 Nginx 在容器里运行起来的最小验证。不过实际使用中,我们不会直接使用容器内默认的配置,而是要将宿主机上的配置文件和网站目录挂载到容器内。
3.2 挂载配置目录和站点目录
Nginx 镜像有几个关键目录:
/etc/nginx/nginx.conf:主配置文件;/etc/nginx/conf.d/:额外的站点配置目录;/usr/share/nginx/html/:默认的静态文件目录。
通过 -v 参数将宿主机目录挂载到容器内。例如:
bash复制docker run --name my-nginx \
-d \
-p 8080:80 \
-v /home/user/nginx/conf.d:/etc/nginx/conf.d:ro \
-v /home/user/nginx/html:/usr/share/nginx/html:ro \
nginx:alpine
这里将宿主机 /home/user/nginx/conf.d 挂载为容器内 /etc/nginx/conf.d,将宿主机 /home/user/nginx/html 挂载为容器内 /usr/share/nginx/html。
:ro 表示只读挂载。配置文件在容器运行期间不应该被容器内进程修改,使用只读挂载更安全。
挂载配置后,修改宿主机上的 Nginx 配置不需要重新构建镜像,只要执行下面的命令重载 Nginx。
bash复制docker exec my-nginx nginx -s reload
这项能力非常重要。它意味着配置文件和容器解耦,开发环境、测试环境、生产环境可以共用一套启动脚本,只是挂载的配置文件不同。
3.3 查看容器日志和进入容器
容器出现问题排查时,日志是第一手资料。
bash复制docker logs my-nginx
实时跟踪日志用 -f:
bash复制docker logs -f my-nginx
进入容器内部操作:
bash复制docker exec -it my-nginx sh
进入后可以检查配置文件语法:
bash复制nginx -t
如果配置语法有误,会明确报出错误文件和行号。
4. Nginx 反向代理配置详解
4.1 反向代理的基本原理
反向代理是 Nginx 最核心的使用场景之一。简单理解就是:客户端请求 Nginx,Nginx 根据规则将请求转发给内部的后端服务,再把后端服务的响应返回给客户端。
在这个过程里,客户端感知到的只有 Nginx 的地址,后端服务的真实地址对外不可见。这样可以实现:
- 隐藏后端服务细节;
- 统一入口管理与安全策略;
- 后端多实例扩展时无感知切换。
4.2 手动创建 Nginx 配置目录
在宿主机上创建如下目录结构。
bash复制mkdir -p /home/user/nginx/conf.d
mkdir -p /home/user/nginx/html
先创建一份最简单的反向代理配置文件。
文件路径:/home/user/nginx/conf.d/web.conf
nginx复制server {
listen 80;
server_name localhost;
location / {
proxy_pass http://127.0.0.1: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;
}
}
这里要注意,当 Nginx 运行在容器内时,127.0.0.1 指向的是容器自身,而不是宿主机。如果后端服务也运行在宿主机上,需要将 127.0.0.1 替换为宿主机在 Docker 网络中的网关地址,通常是 172.17.0.1。
修改后的配置:
nginx复制proxy_pass http://172.17.0.1:3000;
验证配置语法:
bash复制docker exec my-nginx nginx -t
确认无误后重载 Nginx。
4.3 配置 HTTPS 反向代理
生产环境中,Nginx 通常需要配置 HTTPS。
假设证书文件已经在宿主机目录下,路径为 /home/user/nginx/cert/example.com.pem 和 /home/user/nginx/cert/example.com.key。
启动容器时需要将证书目录一并挂载进来。
bash复制docker run --name my-nginx \
-d \
-p 80:80 \
-p 443:443 \
-v /home/user/nginx/conf.d:/etc/nginx/conf.d:ro \
-v /home/user/nginx/cert:/etc/nginx/cert:ro \
nginx:alpine
配置文件内容参考如下。
nginx复制server {
listen 443 ssl;
server_name example.com;
ssl_certificate /etc/nginx/cert/example.com.pem;
ssl_certificate_key /etc/nginx/cert/example.com.key;
ssl_protocols TLSv1.2 TLSv1.3;
location / {
proxy_pass http://172.17.0.1: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;
}
}
server {
listen 80;
server_name example.com;
return 301 https://$host$request_uri;
}
第一个 server 块负责处理 HTTPS 请求,第二个 server 块将所有 HTTP 流量 301 跳转到 HTTPS。
这里需要强调一个高频问题:net::ERR_CERT_COMMON_NAME_INVALID 这类报错,通常不是 Nginx 配置问题,而是浏览器在访问时发现证书域名与当前 URL 域名不一致。排查方向集中在证书是否匹配当前域名、是否使用了自签名证书、证书链是否完整这几项。
4.4 同一服务器部署多个站点
一台服务器部署多个站点是常见需求,Nginx 通过 server_name 区分不同域名。
文件路径:/home/user/nginx/conf.d/php-site.conf(以 PHP 站点为例)
nginx复制server {
listen 80;
server_name php.example.com;
root /usr/share/nginx/html/php-site;
index index.html index.php;
location ~ \.php$ {
fastcgi_pass 172.17.0.1:9000;
fastcgi_index index.php;
include fastcgi_params;
}
}
文件路径:/home/user/nginx/conf.d/node-site.conf
nginx复制server {
listen 80;
server_name node.example.com;
location / {
proxy_pass http://172.17.0.1:3001;
proxy_set_header Host $host;
}
}
启动容器时挂载若干个站点目录。
bash复制docker run --name my-nginx \
-d \
-p 80:80 \
-v /home/user/nginx/conf.d:/etc/nginx/conf.d:ro \
-v /home/user/nginx/html:/usr/share/nginx/html:ro \
nginx:alpine
这样,php.example.com 和 node.example.com 会走不同的后端服务。开发环境中,如果要在本地模拟多域名,可以在宿主机的 hosts 文件中添加解析记录。
bash复制127.0.0.1 php.example.com node.example.com
5. Nginx 负载均衡配置
5.1 upstream 负载均衡组
当后端服务存在多个实例时,Nginx 通过 upstream 配置一组后端节点。
nginx复制upstream backend_servers {
server 172.17.0.1:3001;
server 172.17.0.1:3002;
server 172.17.0.1:3003;
}
server {
listen 80;
server_name app.example.com;
location / {
proxy_pass http://backend_servers;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
在 upstream 中列出的节点,会按照轮询方式依次接收请求。默认策略下,3001、3002、3003 的访问权重相同。
5.2 轮询之外的其他策略
权重轮询。 当服务器性能不一致时,可以通过 weight 分配流量比例。
nginx复制upstream backend_servers {
server 172.17.0.1:3001 weight=3;
server 172.17.0.1:3002 weight=1;
}
上面的配置表示 3001 接收的请求数量约为 3002 的三倍。
ip_hash。 同一个客户端 IP 会被固定分配到同一个后端节点,适用于需要保持会话的场景。
nginx复制upstream backend_servers {
ip_hash;
server 172.17.0.1:3001;
server 172.17.0.1:3002;
}
least_conn。 每次请求优先分配给连接数最少的节点,适合请求处理时间差异较大的场景。
nginx复制upstream backend_servers {
least_conn;
server 172.17.0.1:3001;
server 172.17.0.1:3002;
}
5.3 后端节点故障与重试
网络环境不稳定的情况很常见。Nginx 默认会将在规定时间内连接失败的节点标记为不可用,并将请求转发到其他节点。
可以通过 max_fails 和 fail_timeout 调整故障检测策略。
nginx复制upstream backend_servers {
server 172.17.0.1:3001 max_fails=3 fail_timeout=30s;
server 172.17.0.1:3002 max_fails=3 fail_timeout=30s;
}
含义是:30 秒内如果某个节点出现 3 次失败,Nginx 会在接下来的 30 秒内不再将新请求转发给该节点,等待它恢复后再重新纳入流量分配。
6. Docker Compose 编排:让部署更省心
上面我们使用 docker run 启动了一个 Nginx 容器。但在真实项目中,往往还需要同时运行后端服务和数据库。逐个手动执行 docker run 非常低效,而且重启参数容易丢失。
Docker Compose 通过一个 YAML 文件描述整套服务,一条命令即可启动、停止、重建所有容器。
6.1 docker-compose.yml 基础结构
先创建项目目录。
bash复制mkdir -p /home/user/docker-nginx-demo
cd /home/user/docker-nginx-demo
在这个目录下创建 docker-compose.yml。
yaml复制services:
web:
image: nginx:alpine
container_name: nginx-web
ports:
- "80:80"
- "443:443"
volumes:
- ./conf.d:/etc/nginx/conf.d:ro
- ./html:/usr/share/nginx/html:ro
- ./cert:/etc/nginx/cert:ro
restart: unless-stopped
启动服务。
bash复制docker compose up -d
查看当前服务的运行状态。
bash复制docker compose ps
停止并删除服务。
bash复制docker compose down
6.2 Nginx + Node.js 服务一起编排
下面给出一个更完整的示例,Nginx 作为入口,后端服务是一个 Node.js 应用。
项目结构:
text复制docker-nginx-demo/
├── docker-compose.yml
├── app/
│ ├── Dockerfile
│ └── index.js
└── nginx/
├── conf.d/
│ └── app.conf
└── html/
app/Dockerfile:
dockerfile复制FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 3000
CMD ["node", "index.js"]
app/index.js:
javascript复制const http = require('http');
const server = http.createServer((req, res) => {
res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8' });
res.end('Hello from Node.js container!');
});
server.listen(3000, () => {
console.log('Server is running on port 3000');
});
nginx/conf.d/app.conf:
nginx复制server {
listen 80;
server_name localhost;
location / {
proxy_pass http://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_pass 指向的是 http://app:3000,而不是 IP 地址。这是因为在同一个 Compose 网络内,服务名会作为容器间的 DNS 解析名称直接通信。
docker-compose.yml:
yaml复制services:
nginx:
image: nginx:alpine
container_name: gateway-nginx
ports:
- "80:80"
volumes:
- ./nginx/conf.d:/etc/nginx/conf.d:ro
depends_on:
- app
app:
build: ./app
container_name: backend-app
expose:
- "3000"
这里后端服务只使用 expose 暴露端口,表示仅允许容器内部网络访问,宿主机无法直接访问 3000 端口。真正的入口只有 Nginx 的 80 端口。
启动整套服务:
bash复制docker compose up -d
访问 http://localhost,就能看到 Node.js 服务返回的内容。
这种方式非常接近生产环境的小型部署。后端服务不需要绑定宿主机端口,避免了端口冲突;前端入口统一走 Nginx;升级后端服务时,只需要重建 app 容器。
7. 常见问题与排查思路
7.1 常见报错速查表
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
| Docker Desktop 启动失败,提示 virtualisation support wasn't detected | BIOS 虚拟化未开启或 Windows 功能缺少组件 | 开启 BIOS 虚拟化,确认 WSL2 与虚拟机平台功能 |
| 容器启动成功但访问不了页面 | 端口映射错误或防火墙拦截 | 检查 docker ps 映射,检查宿主机防火墙规则 |
| Nginx 配置改了不生效 | 没有执行 reload | 执行 docker exec <容器名> nginx -s reload |
| 容器内访问宿主机服务失败 | 使用了 127.0.0.1 指向容器自身 |
改为 172.17.0.1 或使用 Compose 服务名 |
net::ERR_CERT_COMMON_NAME_INVALID |
证书域名与访问域名不一致 | 核对证书覆盖域名、检查 hosts 解析、确认证书链完整 |
| Nginx 返回 502 Bad Gateway | 后端服务未启动或反向代理地址错误 | 确认后端容器运行状态,使用 docker logs 查看 Nginx 日志 |
| Nginx 返回 403 Forbidden | 静态目录不存在或目录无读取权限 | 检查挂载路径和站点根目录是否存在 |
| 镜像拉取超时或失败 | 网络原因 | 配置镜像加速器后重新拉取 |
7.2 容器网络不通怎么排查
容器网络问题在项目初期出现频率很高。
第一步,确认容器是否在运行。
bash复制docker ps -a
第二步,进入 Nginx 容器测试能否连通后端服务。
bash复制docker exec -it gateway-nginx sh
然后在容器内执行:
bash复制wget -qO- http://app:3000
如果不能访问,检查 Compose 文件中的服务名是否写错,或者是否将 Nginx 与后端服务定义在了同一个 Compose 网络中。
第三步,查看 Nginx 错误日志。
bash复制docker logs gateway-nginx
日志中如果包含 connect() failed (111: Connection refused),说明 Nginx 能解析到服务名,但后端进程没有在对应端口监听。此时进入后端容器检查服务是否启动。
7.3 Nginx 挂载 conf.d 报错的应对
使用 alpine 版本的 Nginx 镜像时,挂载 conf.d 后偶尔会看到类似 [emerg] 1#1: open() "/etc/nginx/conf.d/default.conf" failed 的报错。
出现这种情况,多数是因为宿主机目录是空的,或者目录权限不足。可以先在宿主机确认目录内容。
bash复制ls -la /home/user/nginx/conf.d/
如果目录为空,往里面放一个合法的配置文件后再重启容器。另外,可以通过只读挂载 :ro 避免运行过程中出现权限类问题。
7.4 端口被占用如何处理
启动容器时如果提示端口被占用,例如:
bash复制Error response from daemon: driver failed programming external connectivity on endpoint
先查看端口被哪个进程占用。
bash复制sudo lsof -i :80
如果能定位到本机已安装的 Nginx 或其他 Web 服务,可以先停掉它,也可以修改容器的端口映射,比如将宿主机端口改为 8080。
8. 最佳实践与工程建议
8.1 命名与目录规范
每个容器都要有明确的名字,避免默认乱码名称影响识别。
bash复制docker run --name gateway-nginx -d ...
在 Compose 项目中,配置文件按项目目录集中存放:
text复制project/
├── docker-compose.yml
├── nginx/
│ ├── conf.d/
│ └── cert/
└── backend/
8.2 配置管理
- 线上配置和本地配置要分离,不要本地改完直接覆盖生产配置。
- 每次改动 Nginx 配置后,先执行
docker exec <容器名> nginx -t检查语法,再执行 reload。 - 配置文件建议纳入 Git 仓库管理,方便回溯和协同。
- 证书文件不要提交到 Git,可以考虑通过部署平台的密钥管理功能挂载。
8.3 日志集中处理
单容器场景直接使用 docker logs 即可。多容器或生产环境建议将容器日志输出重定向到宿主机指定目录,或者接入日志收集系统。
bash复制docker logs -f gateway-nginx > /var/log/nginx/container.log 2>&1
更推荐在 Compose 文件中配置日志驱动和轮转策略,避免日志无限增长占满磁盘。
8.4 资源限制
生产环境不建议让容器无限使用宿主机资源。运行单个容器时可以通过参数限制:
bash复制docker run --name gateway-nginx \
--memory="512m" \
--cpus="1.0" \
-d nginx:alpine
在 Compose 中对应写法:
yaml复制services:
nginx:
image: nginx:alpine
deploy:
resources:
limits:
cpus: "1.0"
memory: 512M
合理的资源限制可以防止某个容器异常占用大量 CPU 或内存,拖垮同一台服务器上的其他服务。
8.5 安全注意事项
- Nginx 容器不要以 root 用户运行,可以通过
user指令在配置中指定运行用户,或者使用支持非 root 运行的镜像。 - 只暴露必要的端口,容器内部端口不要随意映射到宿主机。
- 反向代理到后端时,注意设置超时时间,避免后端长时间不响应导致连接堆积。
- 涉及证书、密钥等敏感文件时,挂载目录权限要收紧,并遵循最小权限原则。
- 对外部不可信的请求头字段保持警惕,不要将
Host、X-Forwarded-For等头简单地透传到后端应用,必要时在代码侧做合法性校验。
9. 下一步可以继续深入的方向
看到这里,你实际上已经掌握了一条完整的容器化部署链路:Docker 管理服务运行环境,Nginx 作为流量入口承担反向代理与负载均衡,Compose 把多服务编排到一起。
下一步可以尝试这样几个方向:
第一,将 Nginx 接入 Let's Encrypt 等证书签发流程,实现 HTTPS 证书的自助申请与自动续期,进一步简化线上配置流程。
第二,学习 Nginx 缓存配置,为静态资源或后端接口配置缓存策略,减少后端服务压力。
第三,在 Docker Compose 基础上加入 CI/CD 流程,提交代码后自动构建镜像并部署到测试环境。
第四,如果你的服务已经有多实例部署需求,可以继续了解 Nginx 的 TCP/UDP 负载均衡(stream 模块),或者逐步过渡到 Kubernetes 体系下使用 Ingress 管理流量。
回到最实际的问题:如果你现在需要在一个空环境里快速让 Nginx 跑起来并接管几个站点和服务的流量,可以按本文的“安装 Docker → 拉取镜像 → 挂载配置 → 启动容器 → 配置反代 → 编排多服务”这个顺序走一遍。每一步的命令和配置都已经覆盖到,遇到报错时回到第 7 节对照排查,大多数常规问题都能在五分钟内定位。把这些操作多练几遍,后续不管是搭建开发环境还是维护线上服务,都会顺手很多。
