🔥 面试官:说说 Docker 和 Nginx 反向代理?我从一个 5 行的 Demo 讲了 20 分钟
摘要:本文从一个真实的 Node.js Demo 出发,一步步引出 Docker 的必要性、核心命令、Nginx 反向代理原理。不罗列概念,只讲"为什么要用"和"用了能解决什么问题"。附赠 15 道面试高频问答,建议收藏!
📌 前言
上周面试,面试官问了我一个问题:
"你用过 Docker 吗?讲讲为什么需要它?"
我没有直接背概念,而是从一个我写过的 5 行 Node.js Demo 讲起,一路讲到 Docker 容器化、Nginx 反向代理、端口映射、配置挂载……面试官频频点头。
这篇文章就是这个思路的完整版——从一个 Demo 出发,搞懂 Docker 到底在解决什么问题。
📚 一、一切从一个 5 行的 Demo 开始
1.1 我写了一个最简单的 HTTP 服务
// index.js
const http = require('http');
const server = http.createServer((req, res) => {
res.end("hello world");
});
server.listen(1314, '0.0.0.0', () => {
console.log('server is running at http://0.0.0.0:1314');
});
启动它:
node index.js
# 访问 http://localhost:1314 → 显示 "hello world"
看起来没问题。但面试官追问了一句:
"你这个服务要部署到服务器上,怎么部署?"
1.2 不用 Docker,部署有多痛苦?
我的电脑是 Node 22,这个项目用的是 CommonJS 的 require,跑得没问题。
但如果服务器上装的是 Node 14?或者同事的电脑是 Node 18?版本不对,直接报错。
你电脑 同事电脑 测试服务器 生产服务器
Node 22 Node 18 Node 14 Node 20
✅ ❌ ❌ ❌
"我电脑上能跑啊!" —— 这句话说了多少次,就被骂了多少次。
问题的根源:每个人的环境不一样。Node 版本、npm 版本、系统库版本……任何一个对不上,就跑不起来。
1.3 Docker 的解法:把环境装进盒子里
Docker 的核心思想就一句话:
把代码 + 运行环境打包成一个"容器",在哪都能跑。
┌─────────────────────────┐
│ Docker 容器 │
│ ┌───────────────────┐ │
│ │ Node 16 │ │
│ │ npm 8 │ │
│ │ index.js │ │
│ │ 所有依赖 │ │
│ └───────────────────┘ │
└─────────────────────────┘
你电脑 同事电脑 测试服务器 生产服务器
✅ ✅ ✅ ✅
环境一模一样,不存在"跑不起来"的问题。
🎭 踩坑小剧场:我第一次用 Docker
第一次用 Docker 的时候,我信心满满地在服务器上跑 docker run -d my-app,然后发现……访问不了。排查了半小时,才发现没加 -p 端口映射。容器里的服务跑得好好的,但外面根本访问不到。
教训:容器是隔离的!不加 -p,外部就访问不到。不加 -v,改了配置不生效。这两个参数不是"可选",是"必须"。
📚 二、动手!用 Docker 跑我的 Demo
2.1 第一步:拉一个 Node 镜像
docker pull node:16
这句话在干嘛?
就像你在应用商店下载一个 App。Docker Hub 是"镜像商店",node:16 是别人打包好的"Node 16 环境光盘"。
Docker Hub(镜像商店)
↓ docker pull
你的电脑(本地镜像仓库)
↓ docker run
运行中的容器
2.2 第二步:把我的 Demo 跑起来
docker run -it -v D:\project:/app -w /app node:16 node index.js
拆开看每一段在干嘛:
| 参数 | 含义 | 解决什么问题 |
|---|---|---|
-it | 打开一个交互终端 | 能看到输出 |
-v D:\project:/app | 把本地文件夹映射到容器里 | 容器能读到我的代码 |
-w /app | 设置工作目录 | 进去就在 /app 目录 |
node:16 | 使用 Node 16 镜像 | 固定环境版本 |
node index.js | 执行的命令 | 启动我的服务 |
等等,为什么要 -v 映射?
因为容器的文件系统是独立的,它看不到你电脑上的文件。-v 就是在两个世界之间开一条隧道:
你的电脑 Docker 容器
D:\project\ /app/
├── index.js ←────→ ├── index.js
├── package.json ←───→ ├── package.json
└── node_modules ←───→ └── node_modules
不映射?容器里啥都没有,node index.js 直接报错:file not found。
📚 三、服务跑起来了,但访问不了?—— 端口映射
3.1 问题:容器里的端口,外面访问不到
我的服务监听的是 1314 端口。在容器里跑起来后:
# 在容器内部
curl localhost:1314 # ✅ 能访问
# 在你的电脑上(宿主机)
curl localhost:1314 # ❌ 连不上!
为什么?
因为容器有自己独立的网络,和你的电脑是"两个世界"。容器里的 1314 端口,和你电脑上的 1314 端口,不是同一个端口。
你的电脑网络 容器网络
┌──────────────┐ ┌──────────────┐
│ :80 │ │ :80 │
│ :1314 ──── │── 防火墙 ──│── :1314 │ ← 不通!
│ :3306 │ │ :3306 │
└──────────────┘ └──────────────┘
3.2 解法:端口映射 -p
docker run -it -p 1314:1314 -v D:\project:/app -w /app node:16 node index.js
-p 13114:1314 的意思:把电脑的 1314 端口和容器的 1314 端口打通。
用户访问 localhost:1314
↓
你的电脑 :1314
↓ ← -p 1314:1314 打通
容器 :1314
↓
Node.js 服务返回 "hello world"
↓
原路返回给用户
现在访问 http://localhost:1314,终于看到 hello world 了!
面试金句:
端口映射就是在容器的隔离网络上开一个门。冒号左边是宿主机端口,右边是容器端口。不映射,外部就访问不到容器里的服务。
🎭 踩坑小剧场:localhost 的陷阱
有一次我在容器里写了个脚本访问宿主机的 MySQL,用的是 localhost:3306,怎么都连不上。debug 了一小时,最后才搞明白:容器里的 localhost 指的是容器自己,不是宿主机!
# 在容器里
curl localhost:3306 # ❌ 连的是容器自己的 3306,不是宿主机的!
curl host.docker.internal:3306 # ✅ 这才是宿主机
教训:容器有自己独立的网络栈。localhost = 容器自己。要访问宿主机,用 host.docker.internal。
📚 四、80 端口 + Nginx 反向代理
4.1 新需求:用户访问 80 端口
我的服务跑在 1314 端口,但用户访问网站一般用 80 端口(HTTP 默认端口)。
能不能让用户访问 localhost:80 就能看到我的服务?
最简单的办法:把端口映射改成 -p 80:1314?可以,但不专业。
生产环境的正确做法:用 Nginx 做反向代理。
4.2 什么是反向代理?
先说"代理"。代理就是中间人。
正向代理(代理客户端):
你 → 代购 → 海外商家
商家不知道是你买的,只知道代购
反向代理(代理服务器):
你 → 服务员 → 后厨
你不知道菜是谁做的,只知道服务员
Nginx 就是那个"服务员"。用户访问 80 端口,Nginx 收到请求,转发给真正的后端服务(1314 端口)。
用户 → localhost:80 → Nginx(服务员)→ Node.js 服务(后厨)
↑
用户不知道这里
4.3 为什么不能直接用 -p 80:1314?
| 方案 | 优点 | 缺点 |
|---|---|---|
-p 80:1314 | 简单直接 | 不能负载均衡、不能缓存、不能做 HTTPS |
| Nginx 反向代理 | 专业、可扩展 | 需要配置 |
生产环境需要 Nginx 的能力:
- 负载均衡:10 个请求分给 3 台服务器
- 静态资源缓存:图片、CSS 直接返回,不打扰后端
- HTTPS 终端:SSL 证书统一管理
- 请求过滤:挡住恶意请求
4.4 写 Nginx 配置文件
# nginx.conf
events {}
http {
server {
listen 80; # 监听 80 端口
location / {
proxy_pass http://host.docker.internal:1314; # 转发给后端
proxy_set_header Host $host; # 保留原始请求头
}
}
}
host.docker.internal 是什么?
这是 Docker 提供的特殊域名,意思是"宿主机的 IP"。
容器里的
localhost指的是容器自己,不是你的电脑!要用host.docker.internal才能访问到宿主机上的服务。
4.5 用 Docker 启动 Nginx
docker run \
--name my-nginx \
-p 80:80 \
-v D:\project\nginx.conf:/etc/nginx/nginx.conf \
-d nginx
拆开看:
| 参数 | 含义 | 为什么需要 |
|---|---|---|
--name my-nginx | 给容器起个名字 | 方便管理 |
-p 80:80 | 端口映射 | 用户能访问到 Nginx |
-v ...\nginx.conf:/etc/nginx/nginx.conf | 配置文件映射 | 用我的配置,不用默认的 |
-d | 后台运行 | 终端关了容器还在 |
nginx | 使用 nginx 镜像 | Docker Hub 下载的 |
为什么又要 -v?
Nginx 镜像自带一个默认配置,但我要用我自己的配置(代理到 1314 端口)。-v 把我的配置文件"塞进"容器里,替换掉默认的:
你的电脑 Docker 容器
nginx.conf ←── -v 映射 ──→ /etc/nginx/nginx.conf
(你的配置) (Nginx 读取的配置)
不映射?Nginx 用默认配置,不会代理到 1314,访问 80 端口看到的是 Nginx 默认欢迎页。
4.6 完整链路
用户访问 localhost:80
↓
你的电脑 :80
↓ ← -p 80:80 端口映射
Docker 容器 :80
↓ ← Nginx 监听 80
nginx.conf 配置
↓ ← proxy_pass 转发
host.docker.internal:1314
↓ ← 回到宿主机
Node.js 服务 :1314
↓
返回 "hello world"
↓
原路返回给用户
用户全程只知道 80 端口,完全不知道背后是 1314 端口的服务在处理。这就是反向代理。
4.7 Nginx 进阶:负载均衡 + 静态资源 + HTTPS
上面只是最基础的反向代理。生产环境的 Nginx 配置要复杂得多:
events {
worker_connections 1024;
}
http {
# ===== 负载均衡:多台后端服务器 =====
upstream backend {
server host.docker.internal:1314 weight=3; # 权重 3,分到更多请求
server host.docker.internal:1315 weight=1; # 权重 1
server host.docker.internal:1316 backup; # 备用,其他挂了才用
}
# ===== 静态资源缓存 =====
server {
listen 80;
server_name localhost;
# 静态文件直接返回,不打扰后端
location /static/ {
alias /usr/share/nginx/static/;
expires 30d; # 缓存 30 天
add_header Cache-Control "public";
}
# 动态请求转发给后端
location / {
proxy_pass http://backend;
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_connect_timeout 5s; # 连接超时
proxy_read_timeout 30s; # 读取超时
}
}
# ===== HTTPS 配置 =====
server {
listen 443 ssl;
server_name www.example.com;
ssl_certificate /etc/nginx/ssl/cert.pem; # 证书
ssl_certificate_key /etc/nginx/ssl/key.pem; # 私钥
ssl_protocols TLSv1.2 TLSv1.3;
location / {
proxy_pass http://backend;
}
}
# HTTP 自动跳转 HTTPS
server {
listen 80;
server_name www.example.com;
return 301 https://$host$request_uri;
}
}
为什么 -p 80:1314 做不到这些?
| Nginx 能力 | -p 80:1314 能做吗? |
|---|---|
| 负载均衡(多台后端) | ❌ 只能映射一个端口 |
| 静态资源缓存 | ❌ 所有请求都打到后端 |
| HTTPS 证书管理 | ❌ 需要后端自己处理 SSL |
| 限流/防刷 | ❌ 完全没有 |
| 请求头改写 | ❌ 透传原始请求 |
| 灰度发布 | ❌ 无法按规则分流 |
面试金句:
Nginx 不只是做端口转发,它是生产环境的"门卫"。负载均衡、缓存、HTTPS、限流、灰度发布都是它的能力。直接用
-p映射端口,等于把门卫撤了。
🎭 踩坑小剧场:Nginx 配置改了不生效
改完 nginx.conf,重启了 Nginx 容器,发现配置没变。排查发现:用的是 -v 挂载的配置文件,但改的是另一个目录下的同名文件。
# 我改的是这个文件
D:\project\nginx.conf
# 但挂载的是这个
-v D:\workspace\nginx.conf:/etc/nginx/nginx.conf # 路径写错了!
教训:-v 映射的路径要绝对准确。改完配置后用 docker exec my-nginx nginx -t 检查语法,再 docker restart my-nginx 重启。
📚 五、Dockerfile:把整个项目打包成镜像
5.1 目前的问题
前面的做法有个问题:每次部署都要手动拉 Node 镜像、映射文件、启动服务。能不能把整个项目打包成一个镜像,一行命令跑起来?
5.2 写一个 Dockerfile
# 基于 Node 16 镜像
FROM node:16-alpine
# 设置工作目录
WORKDIR /app
# 复制 package.json 并安装依赖(利用缓存)
COPY package*.json ./
RUN npm install --production
# 复制代码
COPY . .
# 声明端口
EXPOSE 1314
# 启动命令
CMD ["node", "index.js"]
每一步在干嘛:
| 指令 | 做什么 | 为什么 |
|---|---|---|
FROM node:16-alpine | 基础环境 | alpine 版本更小 |
WORKDIR /app | 设置工作目录 | 后续命令都在这执行 |
COPY package*.json ./ | 先复制依赖清单 | 利用 Docker 缓存层 |
RUN npm install | 安装依赖 | 构建时执行 |
COPY . . | 复制代码 | 代码变了不会重新装依赖 |
EXPOSE 1314 | 声明端口 | 文档作用,不实际映射 |
CMD ["node", "index.js"] | 启动命令 | 容器启动时执行 |
5.3 构建并运行
# 构建镜像("刻光盘")
docker build -t my-app:1.0 .
# 运行镜像("播放光盘")
docker run --name my-app -p 1314:1314 -d my-app:1.0
现在整个项目就是一个镜像了。换任何电脑,只要有 Docker,一行命令就能跑。
5.4 .dockerignore:别把垃圾打包进去
# .dockerignore
node_modules
.git
.env
*.log
dist
.DS_Store
为什么要加?
不加 .dockerignore,COPY . . 会把所有文件都复制进镜像:
| 文件 | 打进去会怎样 |
|---|---|
node_modules/ | 镜像体积暴增,可能覆盖容器内的依赖 |
.git/ | 几百 MB 的 Git 历史,毫无用处 |
.env | 密码泄露! 任何拉取镜像的人都能看到 |
dist/ | 本地旧的构建产物,和容器内冲突 |
5.5 多阶段构建(进阶)
5.4 多阶段构建(进阶)
如果项目需要构建(比如 TypeScript → JavaScript),可以用多阶段构建:
# ===== 阶段 1:构建 =====
FROM node:16 AS builder
WORKDIR /app
COPY . .
RUN npm install
RUN npm run build # TypeScript 编译
# ===== 阶段 2:运行 =====
FROM node:16-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist # 只复制构建产物
COPY --from=builder /app/package*.json ./
RUN npm install --production
CMD ["node", "dist/index.js"]
为什么要多阶段?
单阶段:源码 + node_modules + 构建工具 = ~800MB
多阶段:只有产物 + 生产依赖 = ~150MB
镜像小 = 传输快 = 部署快 = 省钱。
🎭 踩坑小剧场:密码被打进镜像了
有一次我把 .env 文件打包进了 Docker 镜像,推到了 Docker Hub。同事提醒我:任何拉取这个镜像的人都能看到 .env 里的内容!
# 别人拉取你的镜像后
docker run -it my-app cat .env
# 所有环境变量一览无余:数据库密码、API Key……
教训:永远把 .env 加到 .dockerignore。敏感信息用 -e 环境变量传入,或用 Docker Secrets。
🎭 踩坑小剧场:node_modules 的幽灵
有一次构建镜像,明明代码改了,但行为没变。debug 发现:COPY . . 把本地的 node_modules 也复制进去了,覆盖了容器里 npm install 装的依赖。
解决:加一个 .dockerignore:
# .dockerignore
node_modules
.git
.env
*.log
dist
教训:.dockerignore 不是可选的!不加的话,构建镜像时会把所有文件都复制进去,包括 node_modules、.git、敏感配置文件。
📚 六、Docker 网络与安全(进阶重点)
6.1 Docker 网络模式
Docker 容器默认有三种网络模式:
| 模式 | 说明 | 类比 | 使用场景 |
|---|---|---|---|
| bridge | 桥接网络(默认) | 住小区,有自己的门牌号 | 大多数场景 |
| host | 共享宿主机网络 | 住酒店,用酒店的地址 | 需要高性能网络 |
| none | 无网络 | 住山洞,与世隔绝 | 安全隔离场景 |
# 默认 bridge 模式
docker run --network bridge -d nginx
# host 模式:容器直接用宿主机的网络
docker run --network host -d nginx
# 好处:性能好,不需要 -p 端口映射
# 坏处:端口可能冲突,隔离性差
# none 模式:完全无网络
docker run --network none -d nginx
6.2 自定义网络(容器间通信)
# 创建自定义网络
docker network create my-network
# 启动两个容器,加入同一网络
docker run --name app --network my-network -d my-app
docker run --name db --network my-network -d mysql:8.0
# 在 app 容器中直接用容器名访问 db
# docker exec -it app ping db # ✅ 通了!
为什么不用默认 bridge? 默认 bridge 网络中容器只能用 IP 通信,不能用容器名。自定义网络支持 DNS 解析,容器名自动解析为 IP。
6.3 容器安全(生产必备!)
# ❌ 不好:用 root 运行
FROM node:16
WORKDIR /app
COPY . .
CMD ["node", "app.js"] # root 用户运行,有安全风险
# ✅ 好:创建非 root 用户
FROM node:16
WORKDIR /app
# 创建专用用户
RUN addgroup --system appgroup && \
adduser --system --ingroup appgroup appuser
# 复制文件并设置权限
COPY --chown=appuser:appgroup . .
# 切换到非 root 用户
USER appuser
CMD ["node", "app.js"]
为什么要非 root? 如果容器被攻破,root 用户可以做任何事(访问宿主机文件、安装软件)。非 root 用户权限受限,损失可控。
# 扫描镜像漏洞
docker scout cves my-app:1.0
# 查看镜像层
docker history my-app:1.0
🎭 踩坑小剧场:host 模式的坑
有一次我用 --network host 启动 Nginx,想着"性能好,不需要端口映射"。结果服务器上另一个服务已经占了 80 端口,Nginx 直接启动失败。
# host 模式下,容器和宿主机共享端口
# 宿主机 80 被占用 → 容器也用不了 80
docker run --network host -d nginx # ❌ port 80 already in use
教训:host 模式省了 -p,但失去了隔离。生产环境推荐用默认 bridge + 明确的 -p 端口映射。
📚 七、Docker Compose:一键启动所有服务
6.1 目前的痛点
项目有 3 个服务:Node.js 应用 + Nginx + MySQL。每次部署要手动跑 3 条 docker run 命令,记参数、管顺序……太累了。
6.2 docker-compose.yml
version: '3.8'
services:
# Node.js 应用
app:
build: .
ports:
- "1314:1314"
environment:
- NODE_ENV=production
- DB_HOST=db
# Nginx 反向代理
nginx:
image: nginx:latest
ports:
- "80:80"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
depends_on:
- app
# MySQL 数据库
db:
image: mysql:8.0
ports:
- "3306:3306"
environment:
- MYSQL_ROOT_PASSWORD=123456
volumes:
- mysql-data:/var/lib/mysql
volumes:
mysql-data:
6.3 一键启动
# 启动所有服务
docker-compose up -d
# 查看状态
docker-compose ps
# 查看日志
docker-compose logs -f
# 停止所有服务
docker-compose down
一条命令,三个服务全部启动。 不用记参数,不用管顺序,depends_on 帮你处理依赖关系。
6.4 容器间怎么通信?
Docker Compose 自动创建一个网络,容器之间直接用服务名通信:
// 在 app 容器中连接 MySQL
// 不用写 localhost:3306,直接写 db:3306
const connection = mysql.createConnection({
host: 'db', // ← 服务名,不是 localhost!
port: 3306,
user: 'root',
password: '123456'
});
app 容器 → db:3306 → MySQL 容器
↑
Docker Compose 自动创建的网络
🎭 踩坑小剧场:depends_on 的幻觉
depends_on 只保证容器启动顺序,不保证就绪状态。我的 app 容器启动后立刻连 MySQL,结果 MySQL 还在初始化,连接直接报错。
# ❌ 这样不够!
depends_on:
- db
# ✅ 要配合 healthcheck
depends_on:
db:
condition: service_healthy
services:
db:
image: mysql:8.0
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 10s
timeout: 5s
retries: 5
教训:depends_on ≠ 服务就绪。生产环境一定要加 healthcheck。
📚 八、面试高频问答(18 题)
基础篇
Q1:Docker 和虚拟机有什么区别?
Docker 容器共享宿主机内核,秒级启动,MB 级体积。虚拟机需要完整 OS,分钟级启动,GB 级体积。容器适合微服务,虚拟机适合强隔离场景。
Q2:镜像和容器的关系?
镜像是只读模板(光盘),容器是镜像的运行实例(播放器)。一个镜像可以创建多个容器,就像一张光盘可以同时在多台播放器上播放。
Q3:Docker 的镜像分层机制?
Docker 镜像由多个只读层叠加而成。容器启动时在最上面加一个可写层。多个容器共享相同的镜像层,节省磁盘空间。
Q4:docker run -p 80:80 两个 80 分别是什么?
冒号左边是宿主机端口,右边是容器端口。用户访问宿主机的 80,Docker 转发到容器的 80。
Q5:host.docker.internal 是什么?
Docker 提供的特殊 DNS,解析到宿主机 IP。容器内访问宿主机服务时用,避免硬编码 IP。
追问:Linux 上怎么用?需要加
--add-host=host.docker.internal:host-gateway。
Nginx 篇
Q6:正向代理和反向代理的区别?
| 对比项 | 正向代理 | 反向代理 |
|---|---|---|
| 代理对象 | 客户端 | 服务器 |
| 典型场景 | VPN | Nginx |
| 服务端感知 | 不知道真实客户端 | 客户端不知道真实服务器 |
追问:Nginx 还能做什么?负载均衡、静态资源服务、HTTPS 终端、请求过滤、限流。
Q7:为什么不用 -p 80:1314 而用 Nginx?
直接映射简单但不专业。Nginx 能做负载均衡、缓存、HTTPS、限流,生产环境必备。
Dockerfile 篇
Q8:COPY 和 ADD 的区别?
COPY 只复制文件。ADD 能自动解压 tar 包和下载 URL。推荐用 COPY,行为更可预测。
Q9:CMD 和 ENTRYPOINT 的区别?
CMD 可被
docker run参数覆盖,ENTRYPOINT 不会。组合使用时 ENTRYPOINT 是命令,CMD 是默认参数。
Q10:ENV 和 ARG 的区别?
ENV 构建 + 运行时都有效,持久化到镜像。ARG 仅构建时有效,构建后消失。
进阶篇
Q11:怎么优化镜像体积?
- 多阶段构建(构建和运行分离)
- 用 alpine 基础镜像
- 合并 RUN 指令减少层数
- 用 .dockerignore 排除无关文件
Q12:容器挂了怎么办?
--restart always自动重启- HEALTHCHECK 健康检查
docker logs排查原因- 生产环境配监控告警
Q13:两个容器怎么通信?
自定义网络
docker network create,或用 Docker Compose 自动建网络,容器间用服务名通信。
Q14:depends_on 能保证就绪吗?
只保证启动顺序,不保证就绪。要配合 healthcheck 的
condition: service_healthy。
Q15:什么是 .dockerignore?
类似 .gitignore,告诉 Docker 构建时忽略哪些文件。减小镜像体积、加速构建、保护敏感信息。
网络与安全篇
Q16:Docker 的网络模式有哪些?
三种:bridge(默认,桥接网络)、host(共享宿主机网络,性能好但隔离差)、none(无网络,安全隔离)。生产环境推荐 bridge + 明确端口映射。
追问:bridge 和自定义网络有什么区别?默认 bridge 只支持 IP 通信,自定义网络支持容器名 DNS 解析。
Q17:为什么容器要非 root 用户运行?
容器被攻破时,root 用户权限太大,可以做任何事。非 root 用户权限受限,损失可控。Dockerfile 中用
USER指令切换用户。
Q18:Nginx 能做什么?和直接 -p 映射有什么区别?
Nginx 是生产环境的"门卫":负载均衡(多台后端)、静态资源缓存、HTTPS 证书管理、限流防刷、灰度发布。
-p只能做端口转发,这些能力都没有。
📚 九、踩坑排查指南
问题 1:端口被占用
# 报错:port is already allocated
# 排查:谁占了端口
netstat -ano | findstr :80
# 解决:换端口 -p 8080:80
问题 2:容器内 localhost 不通
# 原因:localhost 指向容器自己
# 解决:用 host.docker.internal
curl http://host.docker.internal:1314
问题 3:容器启动立即退出
# 查看原因
docker logs <容器名>
# 常见:主进程退出、配置错误、端口冲突
问题 4:数据丢失
# 原因:没挂载卷,容器删了数据就没了
# 解决:-v mysql-data:/var/lib/mysql 持久化
问题 5:配置改了不生效
# 原因:改的是宿主机的文件,没映射到容器
# 解决:确认 -v 路径正确,重启容器
docker restart <容器名>
问题 6:Docker Compose 容器间通信不通
# 现象:app 容器连 db 容器,报错 "host not found"
# 原因:用了错误的 host
# ❌ 错误:用 localhost 或 IP
DB_HOST=localhost
DB_HOST=172.17.0.2
# ✅ 正确:用服务名
DB_HOST=db # docker-compose.yml 中定义的服务名
教训:Docker Compose 中容器间通信用服务名,不是 localhost,不是 IP。服务名会被自动解析为容器 IP。
💡 十、总结
Docker 在解决什么问题?
没 Docker:环境不一致 → "我电脑上能跑啊"
有 Docker:环境打包 → 在哪都能跑
-p 和 -v 在解决什么问题?
-p 端口映射:容器网络隔离 → 外部访问不到 → 开个门
-v 路径映射:容器文件隔离 → 配置/数据不通 → 开条隧道
从 Demo 到部署的完整链路
1. 写代码(index.js)
2. 用 Docker 跑起来(docker run -it)
3. 端口映射让外部能访问(-p 1314:1314)
4. Nginx 反向代理做 80 端口转发(-v + nginx.conf)
5. Dockerfile 打包成镜像(docker build)
6. .dockerignore 排除垃圾文件
7. Docker Compose 编排多服务(docker-compose.yml)
8. 安全加固:非 root 用户 + 镜像扫描
面试回答口诀
Docker 三件套:镜像、容器、Dockerfile
命令五字诀:拉、跑、查、停、删
代理两兄弟:正向代理客户端,反向代理服务端
映射两桥梁:-p 开门(端口),-v 开隧道(文件)
网络三种桥:bridge 默认、host 共享、none 隔离
编排用 Compose:一个 YAML 搞定全家桶
安全三板斧:非 root、.dockerignore、镜像扫描
🔗 参考资料
💬 交流讨论
这篇文章从一个 5 行 Demo 出发,一路讲到 Docker 容器化、Nginx 反向代理、Dockerfile 构建、Docker Compose 编排、网络安全……面试中被问到 Docker,你打算怎么回答?
欢迎在评论区分享你的面试经历或踩坑故事!我会在评论区抽 3 位同学,帮你 review Docker 面试回答 👇
觉得有用?点个赞👍收藏⭐关注👆,下期更新「Docker + CI/CD 自动化部署」!
📚 系列文章预告:
- 下一篇:《Docker + GitHub Actions 实现 CI/CD 自动化部署》
- 系列:《后端面试全链路:从 Docker 到 K8s》