从"我的电脑能跑,你的电脑跑不了"到"一次构建,到处运行"的华丽转身
📖 前言:一个真实的故事
想象这样一个场景:
你入职一家新公司,接手一个 5 年前用 Vue 2 开发的老项目。项目文档上写着:"请使用 Node 16 + npm 8 运行"。
你自信地打开终端,敲下
node -v,屏幕上赫然显示:v22.0.0。然后你开始了漫长的折腾:卸载 Node 22、安装 Node 16、npm install 报错、缺少 Python 环境、node-gyp 编译失败...
折腾半天,项目还是跑不起来。
这个时候,你一定会想:有没有一种技术,能让"我的电脑能跑,你的电脑也能跑"?
答案就是:Docker。
一、Docker 是什么?
1.1 核心思想
Docker = 应用 + 运行环境 的标准化打包工具
简单说,Docker 把你的应用程序 和它需要的所有依赖(Node.js、Redis、MySQL、Nginx...)一起打包成一个独立的"集装箱"。
生活类比:
想象你是做海运的:
| 方式 | 说明 | 结果 |
|---|---|---|
| 传统方式 | 货物直接扔船上 | 到地方发现包装坏了、温度不对 → 跑不起来 ❌ |
| Docker 方式 | 货物装进标准化集装箱 | 温度、湿度都在箱内控制 → 到哪都能完好无损 ✅ |
1.2 Docker 解决了什么问题?
| 问题 | 传统方式 | Docker 方式 |
|---|---|---|
| 环境不一致 | 开发用 Node 16,生产用 Node 22 → 跑不起来 | 统一在容器中用 Node 16 ✅ |
| 依赖冲突 | 项目 A 需要 Python 2,项目 B 需要 Python 3 | 每个容器隔离,互不影响 ✅ |
| 部署复杂 | 写部署文档:装 Node、配 Nginx、设环境变量... | 一条命令:docker run ✅ |
| 团队协作 | "我电脑上明明是好的啊!" | 每个人跑同一个镜像 ✅ |
二、Docker 核心概念
2.1 三大核心
scss
┌─────────────────────────────────────────────────────────────┐
│ │
│ 🏪 Docker Hub (镜像仓库) │
│ ↓ docker pull │
│ 💿 Image (镜像) → 只读模板,包含应用+环境 │
│ ↓ docker run │
│ 📦 Container (容器) → 可运行的实例 │
│ │
└─────────────────────────────────────────────────────────────┘
① Image(镜像)= 光盘/模具
| 属性 | 说明 |
|---|---|
| 是什么 | 应用程序 + 运行环境的"只读模板" |
| 类比 | 游戏光盘、蛋糕模具、建筑设计图纸 |
| 特点 | 不可修改,可复用 |
| 获取 | 从 Docker Hub 下载 |
bash
docker pull nginx # 下载 nginx 镜像
docker pull node:16 # 下载 Node 16 镜像
docker images # 查看所有镜像
② Container(容器)= 运行中的实例
| 属性 | 说明 |
|---|---|
| 是什么 | 从镜像启动的"可运行进程" |
| 类比 | 放进光驱播放的光盘、烤出来的蛋糕、盖好的房子 |
| 特点 | 可启动、停止、删除,彼此隔离 |
| 关系 | 一个镜像 → 多个容器 |
bash
docker run -d nginx # 从镜像启动容器
docker ps # 查看运行中的容器
docker stop <容器ID> # 停止容器
docker rm <容器ID> # 删除容器
③ Docker Hub = 镜像超市
存放各种官方和社区制作的镜像,就像 GitHub 存代码,Docker Hub 存镜像。
bash
# 常用镜像
docker pull nginx # Web 服务器
docker pull node # Node.js 环境
docker pull mysql # MySQL 数据库
docker pull redis # Redis 缓存
2.2 镜像的层级结构(为什么 Docker 这么快)
yaml
传统虚拟机 (笨重)
├── 完整的操作系统 (几 GB)
├── 运行环境
└── 应用代码
启动时间:几分钟 ❌
Docker 镜像 (轻量)
├── Layer 1: 基础系统 (Ubuntu/Alpine) ← 共享宿主机内核
├── Layer 2: 安装 Node.js ← 可复用
├── Layer 3: 复制 package.json ← 可缓存
├── Layer 4: npm install ← 可缓存
└── Layer 5: 应用代码 ← 新增
启动时间:几秒钟 ✅
三、Docker 实战:用 Nginx 做反向代理
3.1 场景说明
我们有一个 Node.js 应用,运行在 1314 端口。但我们希望用户通过 80 端口访问,并且不需要知道后端端口号。
解决方案:用 Nginx 做反向代理。
markdown
用户访问 localhost:80
↓
Nginx (反向代理)
↓
转发到 localhost:1314
↓
Node.js 应用
3.2 Nginx 是什么?
Nginx = 高性能 Web 服务器 + 反向代理服务器
生活类比:
- Nginx = 公司前台小姐
- 你访问
80端口 → 跟前台说"我要找技术部" - 前台查通讯录(配置文件)→ 带你去 13 楼 14 号房间(1314 端口)
- 你只需要记住公司地址(
localhost),不需要知道具体房间号
3.3 什么是反向代理?
正向代理 vs 反向代理
| 对比维度 | 正向代理 | 反向代理 |
|---|---|---|
| 谁在用 | 客户端(你)主动使用 | 服务器(Nginx)使用 |
| 目的 | 访问受限资源(科学上网) | 隐藏后端、负载均衡、安全 |
| 用户知道吗 | ✅ 知道,要手动配置 | ❌ 不知道,无感知 |
| 隐藏谁 | 隐藏客户端 | 隐藏服务端 |
| 例子 | VPN、公司网络代理 | Nginx、阿里云 SLB |
核心区别一句话:
正向代理 :客户端主动找帮手 反向代理:服务器主动找帮手
反向代理的好处
- 隐藏真实端口 :用户只知道
80,不知道1314 - 灵活迁移 :后端从
1314改成1315,用户无感知 - 负载均衡:可以把请求分发到多台服务器
- 统一 SSL:Nginx 统一处理 HTTPS,后端不用管
3.4 网络基础:端口是什么?
ini
localhost:1314
├── localhost = 本机地址 (127.0.0.1)
└── 1314 = 端口号 (应用程序的"门牌号")
默认端口(必须记住):
| 端口 | 协议/服务 | 说明 |
|---|---|---|
| 80 | HTTP | 浏览器访问时默认端口 |
| 443 | HTTPS | 加密访问默认端口 |
| 3306 | MySQL | 数据库默认端口 |
| 6379 | Redis | 缓存默认端口 |
| 3000 | Node.js | 常用开发端口 |
| 1314 | 自定义 | 你的后端端口 |
重要 :浏览器访问
http://localhost等同于http://localhost:80
3.5 准备 Nginx 配置文件
创建 nginx.conf:
nginx
events {
worker_connections 1024; # 最大并发连接数
}
http {
server {
listen 80; # Nginx 监听 80 端口
location / { # 所有请求
proxy_pass http://host.docker.internal:1314; # 转发到宿主机 1314
proxy_set_header Host $host; # 保持原始 Host 头
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
}
配置文件解析:
| 配置项 | 含义 |
|---|---|
events { worker_connections 1024; } |
Nginx 的"体能配置",能同时接待 1024 个客人 |
http { ... } |
HTTP 服务配置 |
server { ... } |
虚拟主机配置(相当于一个网站) |
listen 80; |
在 80 号门口等着 |
location / { ... } |
所有访问根路径 '/' 的请求 |
proxy_pass http://host.docker.internal:1314; |
"把客人领到 1314 房间" |
proxy_set_header Host $host; |
转发时告诉后端:"是哪个域名访问的" |
关于 host.docker.internal:
- Docker 容器内访问宿主机的专用域名
- Windows/Mac 默认支持
- Linux 需要加
--add-host host.docker.internal:host-gateway
四、Docker 命令实战
4.1 完整操作流程
步骤 1:测试配置文件语法
bash
docker run --rm \
-v C:/path/to/nginx.conf:/etc/nginx/nginx.conf \
nginx nginx -t
输出:
bash
nginx: configuration file /etc/nginx/nginx.conf test is successful ✅
命令解析:
--rm:容器退出后自动删除(测试用)-v:挂载本机文件到容器nginx nginx -t:第一个 nginx 是镜像名,第二个是执行 nginx 测试命令
步骤 2:启动 Nginx 容器
bash
docker run -d \
--name my-nginx \
--add-host host.docker.internal:host-gateway \
-p 80:80 \
-v C:/path/to/nginx.conf:/etc/nginx/nginx.conf \
nginx
参数详解:
| 参数 | 含义 |
|---|---|
-d |
后台运行 (detach) |
--name my-nginx |
给容器起名 |
--add-host host.docker.internal:host-gateway |
容器访问宿主机 |
-p 80:80 |
端口映射:本机 80 → 容器 80 |
-v ... |
挂载配置文件 |
nginx |
使用 nginx 镜像 |
步骤 3:验证容器运行
bash
# 查看运行中的容器
docker ps
输出示例:
bash
CONTAINER ID IMAGE STATUS PORTS NAMES
a1b2c3d4e5f6 nginx Up 10 seconds 0.0.0.0:80->80/tcp my-nginx
bash
# 查看日志
docker logs my-nginx
# 进入容器调试
docker exec -it my-nginx bash
步骤 4:测试访问
bash
# 方式1:浏览器访问
http://localhost
# 方式2:命令行测试
curl http://localhost
# 方式3:查看 Nginx 访问日志
docker logs -f my-nginx
4.2 常用 Docker 命令速查
镜像操作
bash
docker pull nginx # 下载镜像
docker images # 查看所有镜像
docker rmi <镜像ID> # 删除镜像
docker tag <镜像ID> <新标签> # 打标签
容器操作
bash
docker run -d nginx # 启动容器
docker ps # 查看运行中的容器
docker ps -a # 查看所有容器(包括停止的)
docker stop <容器ID> # 停止容器
docker start <容器ID> # 启动已停止的容器
docker restart <容器ID> # 重启容器
docker rm <容器ID> # 删除容器
docker rm -f <容器ID> # 强制删除
调试命令
bash
docker logs <容器ID> # 查看日志
docker logs -f <容器ID> # 实时查看日志
docker exec -it <容器ID> bash # 进入容器
docker exec <容器ID> nginx -s reload # 重载配置
docker inspect <容器ID> # 查看容器详细信息
五、端口映射详解
5.1 端口映射的语法
bash
-p 宿主机端口:容器端口
5.2 不同端口映射的区别
bash
# 本机 80 → 容器 80
docker run -p 80:80 nginx
# 访问:http://localhost
# 本机 8080 → 容器 80
docker run -p 8080:80 nginx
# 访问:http://localhost:8080
# 本机 3000 → 容器 3000
docker run -p 3000:3000 node
# 访问:http://localhost:3000
# 多个端口映射
docker run -p 80:80 -p 443:443 nginx
5.3 为什么开发用 8080,生产用 80?
| 环境 | 端口 | 原因 |
|---|---|---|
| 开发 | 8080、3000、1314 | 不需要管理员权限,不冲突 |
| 生产 | 80、443 | 用户访问方便,专业 |
六、文件挂载详解
6.1 挂载的作用
bash
-v 本机文件路径:容器文件路径
好处:
- 修改本机配置文件 → 容器立即生效
- 不用重新构建镜像
- 方便调试
6.2 挂载的注意点
bash
# ✅ 正确:使用绝对路径
-v C:/Users/xxx/nginx.conf:/etc/nginx/nginx.conf
# ✅ 正确:使用相对路径(已进入目录)
-v ./nginx.conf:/etc/nginx/nginx.conf
# ❌ 错误:只有文件名,Docker 会当成目录
-v nginx.conf:/etc/nginx/nginx.conf
# 报错:not a directory
6.3 Windows 路径格式
bash
# PowerShell 推荐
-v C:/Users/xxx/nginx.conf:/etc/nginx/nginx.conf
# CMD 使用
-v C:\Users\xxx\nginx.conf:/etc/nginx/nginx.conf
# Git Bash/WSL
-v /c/Users/xxx/nginx.conf:/etc/nginx/nginx.conf
七、完整访问链路图
csharp
用户浏览器访问 http://localhost
↓
请求到达本机 80 端口
↓
Docker 端口映射 (-p 80:80)
↓
Nginx 容器接收请求(监听 80)
↓
Nginx 读取配置文件 (nginx.conf)
↓
location / 匹配 → proxy_pass
↓
转发到 http://host.docker.internal:1314
↓
--add-host 解析到宿主机 IP
↓
宿主机 Node.js 应用(1314 端口)
↓
处理请求,返回响应
↓
原路返回给用户浏览器 ✅
八、实战:完整的 Node.js + Nginx 项目
8.1 Node.js 后端 (app.js)
javascript
const http = require('http');
const server = http.createServer((req, res) => {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({
message: 'Hello from Docker!',
path: req.url,
timestamp: new Date().toISOString()
}));
});
const PORT = 1314;
server.listen(PORT, '0.0.0.0', () => {
console.log(`Server running on port ${PORT}`);
});
8.2 Nginx 配置 (nginx.conf)
nginx
events {
worker_connections 1024;
}
http {
server {
listen 80;
location / {
proxy_pass http://host.docker.internal:1314;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
}
8.3 启动脚本
bash
#!/bin/bash
# 1. 启动 Node.js 后端
node app.js &
# 2. 启动 Nginx 容器
docker run -d \
--name my-nginx \
--add-host host.docker.internal:host-gateway \
-p 80:80 \
-v $(pwd)/nginx.conf:/etc/nginx/nginx.conf \
nginx
echo "✅ 服务已启动!访问 http://localhost"
8.4 验证
bash
# 测试 API
curl http://localhost
输出:
json
{"message":"Hello from Docker!","path":"/","timestamp":"2026-08-18T..."}
九、常见问题排查
9.1 容器启动失败 (Exited 1)
bash
# 查看错误日志
docker logs <容器ID>
# 常见原因:
# 1. 配置文件语法错误 → 用 nginx -t 测试
# 2. 端口被占用 → 换端口或 kill 占用进程
# 3. 挂载路径错误 → 检查文件是否存在
9.2 502 Bad Gateway
问题:Nginx 正常运行,但无法连接后端
原因:
- 后端服务未启动
- 后端端口不正确
- 后端监听 localhost 而非 0.0.0.0
- 未加
--add-host参数
解决步骤:
bash
# 1. 检查后端是否运行
netstat -ano | findstr :1314
# 2. 检查后端是否监听 0.0.0.0
# 错误:server.listen(1314, 'localhost')
# 正确:server.listen(1314, '0.0.0.0')
# 3. 测试直接访问后端
curl http://host.docker.internal:1314
# 4. 查看 Nginx 错误日志
docker logs my-nginx
9.3 端口被占用
bash
# Windows:查看端口占用
netstat -ano | findstr :80
# 杀掉进程
taskkill /PID <进程号> /F
# 或换一个端口
docker run -p 8080:80 nginx
9.4 配置文件挂载失败
bash
# 错误:not a directory
# 原因:Docker 把文件名当成了目录
# 解决:使用绝对路径
-v C:/Users/xxx/nginx.conf:/etc/nginx/nginx.conf
# 或进入目录使用相对路径
cd /path/to/config
-v ./nginx.conf:/etc/nginx/nginx.conf
十、最佳实践总结
10.1 开发 vs 生产
| 环境 | 端口 | 配置方式 |
|---|---|---|
| 开发 | 8080/3000 | 直接访问,快速调试 |
| 生产 | 80/443 | Nginx 反向代理 |
10.2 配置文件的"三要素"
markdown
1. listen → 在哪里等(端口)
2. location → 等什么请求(路径)
3. proxy_pass → 去哪儿(转发目标)
10.3 Docker 使用口诀
镜像像图纸,容器像产品
镜像像光盘,容器像播放
镜像像类,容器像对象
端口映射要记牢,宿主容器要分好
配置文件用挂载,修改不用重启了
日志查看很重要,问题在哪早知道
10.4 最佳实践清单
- 开发时用 8080 端口(避免权限问题)
- 生产时用 80 端口(用户访问方便)
- 配置文件用挂载(方便修改)
- 加
--add-host(容器访问宿主机) - 先测试配置再启动(
nginx -t) - 用
docker logs查看错误 - 用
--name给容器命名 - 一个容器只运行一个进程
十一、进阶学习方向
学完本文,你已经掌握了 Docker 的核心概念和 Nginx 反向代理的实战用法。接下来可以学习:
- Docker Compose:同时管理多个容器(nginx + node + mysql)
- Dockerfile:自己构建镜像
- Docker 网络:容器间通信
- Docker 卷:数据持久化
- Kubernetes:大规模容器编排
📝 写在最后
还记得开头的那个场景吗?
"我的电脑能跑,你的电脑怎么跑不了?"
现在你有了答案:Docker。
它就像一个魔法盒子,把你的应用和它需要的所有东西打包在一起。从此:
- ✅ 不再有环境不一致的问题
- ✅ 不再有依赖冲突的痛苦
- ✅ 不再有"我电脑上明明是好的"的尴尬
你今天的收获:
- 理解了 Docker 的核心概念(镜像、容器、仓库)
- 掌握了 Docker 的常用命令
- 学会了 Nginx 反向代理配置
- 完成了从入门到实战的完整流程
下一步:去给你的项目加上 Docker,体验"一次构建,到处运行"的快乐吧!🚀
💡 小贴士:所有命令和配置都在文章中,建议边看边敲,动手实践才能真正掌握。
如果你觉得这篇文章有帮助,欢迎点赞、收藏、分享! 🙌