🔥 面试官:说说 Docker 和 Nginx 反向代理?我从一个 5 行的 Demo 讲了 20 分钟

0 阅读19分钟

🔥 面试官:说说 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

为什么要加?

不加 .dockerignoreCOPY . . 会把所有文件都复制进镜像:

文件打进去会怎样
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:正向代理和反向代理的区别?
对比项正向代理反向代理
代理对象客户端服务器
典型场景VPNNginx
服务端感知不知道真实客户端客户端不知道真实服务器

追问: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:怎么优化镜像体积?
  1. 多阶段构建(构建和运行分离)
  2. 用 alpine 基础镜像
  3. 合并 RUN 指令减少层数
  4. 用 .dockerignore 排除无关文件
Q12:容器挂了怎么办?
  1. --restart always 自动重启
  2. HEALTHCHECK 健康检查
  3. docker logs 排查原因
  4. 生产环境配监控告警
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》