Docker部署深度解析
解决"在我机器上能跑"的终极武器:镜像与容器是什么、多阶段构建怎么把 1GB 镜像压到 30MB、Docker Compose 怎么一键拉起前后端全家桶,以及 CMD/ENTRYPOINT 等易错点。
一句话概括
Docker 把应用连同它的运行环境打包成一个镜像,到任何装了 Docker 的机器上跑出来都一模一样——彻底干掉”我本地是好的”这种甩锅。对前端来说,它是把 Vue/React 构建产物、Node 服务、Nginx、Redis 一股脑编排起来的部署利器。
面试不考你背命令,而考镜像分层与缓存、多阶段构建瘦身、容器和虚拟机的本质区别、CMD 与 ENTRYPOINT 怎么配合。讲清这几个,你就不只是”用过 docker-compose up”。
核心知识点
1. 镜像、容器、Dockerfile:三个核心概念
- 镜像(Image):只读模板,包含代码 + 运行时 + 依赖,相当于”装机盘”
- 容器(Container):镜像跑起来的实例,互相隔离,一个镜像可起多个容器
- Dockerfile:构建镜像的”配方”,逐行指令叠成镜像层
最小可用:把一个静态站点丢进 Nginx 镜像。
1
2
3
4
5
FROM nginx:stable-alpine
COPY ./dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
1
2
3
4
docker build -t my-app:v1 . # 构建
docker run -d -p 8080:80 --name app my-app:v1 # 跑容器
docker ps # 看运行状态
docker stop app && docker rm app # 停并删
2. 多阶段构建:把 1GB 镜像压到 30MB
前端构建需要 Node + node_modules,但运行时只需要静态文件。多阶段构建用 AS builder 专门编译,再把产物拷进干净的小镜像:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# ===== 构建阶段 =====
FROM node:20-alpine AS builder
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci # 构建需要 vite/tsc 等 devDependencies,必须全量装
COPY . .
RUN npm run build
# ===== 运行阶段(只留产物) =====
FROM nginx:stable-alpine
COPY --from=builder /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
⚠️ 常见错误:在构建阶段写
npm ci --omit=dev(原--only=production)——那样 devDependencies 装不上,vite/tsc直接缺失、构建失败。构建阶段要全量装,瘦身靠”换运行阶段”,不是靠构建阶段少装。
| 对比 | 单阶段 | 多阶段 |
|---|---|---|
| 最终镜像 | 1GB+(含 node_modules) | ~30MB(仅 Nginx + 静态文件) |
| 安全风险 | 高(工具一堆) | 低(只留运行时) |
| 缓存利用 | 差 | 好(依赖层可复用) |
3. Node 后端容器化:非 root + 信号转发
后端容器要跑得稳,两个关键:用非 root 用户(被攻破也拿不到宿主 root)、用 tini 当 PID 1(正确转发 SIGTERM,docker stop 才能优雅退出)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
FROM node:20-alpine AS builder
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --omit=dev # 运行只需生产依赖
FROM node:20-alpine
RUN apk add --no-cache tini # 轻量 init,处理信号
RUN addgroup -S nodejs && adduser -S nodejs # 建非 root 用户
WORKDIR /app
COPY --from=builder /app/node_modules ./node_modules
COPY . .
ENV NODE_ENV=production
USER nodejs # 降权运行
EXPOSE 3000
ENTRYPOINT ["/sbin/tini", "--"]
CMD ["node", "dist/server.js"]
4. Docker Compose:一键拉起全家桶
多服务(前端 + 后端 + Redis + 数据库)用 Compose 一个文件编排。注意 Compose v2 已把 version 字段标记为 obsolete(会告警),现代写法直接省略,按实际用到的功能来。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# docker-compose.yml(Compose v2:无需 version 字段)
services:
frontend:
build: ./frontend
ports: ["80:80"]
depends_on: [api]
restart: unless-stopped
api:
build: ./backend
expose: ["3000"]
environment:
- NODE_ENV=production
- REDIS_URL=redis://redis:6379
depends_on: [redis]
restart: unless-stopped
redis:
image: redis:7-alpine
volumes: ["redis_data:/data"]
restart: unless-stopped
volumes:
redis_data:
1
2
3
4
docker compose up -d --build # 构建并后台启动
docker compose ps # 看状态
docker compose logs -f api # 跟 api 日志
docker compose down # 停并删容器/网络
5. 镜像分层与缓存:指令顺序有讲究
镜像是一层层叠的,每层是上一层的增量。Docker 会缓存没变的层——所以不常变的放前面、常变的放后面,最大化复用:
1
2
3
4
COPY package.json package-lock.json ./ # 依赖文件先拷
RUN npm ci # 只有依赖变了才重装
COPY . . # 源码常变,放后面
RUN npm run build
配 .dockerignore 排除 node_modules、.git、.env 等,避免它们被塞进构建上下文拖慢速度。
其实你每天都在用
- 本地起一套依赖:
docker compose up一键拉起 MySQL + Redis,不用本机装环境 - 统一团队环境:新人 clone 下来直接跑,告别”你环境没配好”
- 前端构建产物托管:多阶段构建把
dist丢进 Nginx 镜像,部署即静态服务 - CI/CD 出镜像:Git 推上去 → 流水线
docker build+push到镜像仓库 - 多项目隔离:同一台机器跑多个前端站点,各占各的容器互不打架
- 本地调试后端:把本地 Node 服务连到容器里的 Redis,环境一致
- 回滚版本:镜像打
v1.2.3标签,出问题直接docker run旧标签秒回退
常见误解(FAQ)
❌ 误区一:”Docker 容器就是轻量虚拟机”
本质不同。虚拟机跑完整 Guest OS,靠 Hypervisor 虚拟化硬件,GB 级、分钟级启动;容器共享宿主内核,只隔离进程/网络/文件系统(namespace + cgroups),MB 级、秒级启动。所以容器是”进程级隔离”,不是”操作系统级隔离”,性能损耗几乎为零。
❌ 误区二:”CMD 和 ENTRYPOINT 随便用一个就行”
分工明确:RUN 是构建时执行(装依赖、编译);CMD 是容器启动时默认命令,可被 docker run 后面的参数覆盖;ENTRYPOINT 是固定入口,一般不覆盖。常见组合是 ENTRYPOINT 定死启动器、CMD 给默认参数:
1
2
ENTRYPOINT ["/sbin/tini", "--"] # 固定:用 tini 兜底信号
CMD ["node", "dist/server.js"] # 可被 docker run 镜像 node other.js 覆盖
❌ 误区三:”镜像越小越好,所以 npm install 用 –no-cache”
两个坑:① npm ci 根本没有 --no-cache 选项(那是 docker build --no-cache 的),写上去会报错;正确是用 npm ci(基于 lockfile、干净可复现)代替 npm install。② 真正瘦身靠多阶段构建 + alpine 基础镜像 + 运行阶段 --omit=dev,而不是在构建阶段少装依赖——构建阶段少装会导致编译失败。
❌ 误区四:”docker-compose.yml 顶部必须写 version: ‘3.8’“
那是 Compose v1 的写法。Compose v2 已把 version 标记为 obsolete(运行会告警,但兼容),现代 Compose 文件直接省略 version,按实际功能声明即可。留着不报错,但属于过时写法,新项目建议去掉。
一句话总结
Docker 的价值就一句:把”环境”也变成了可版本化、可复制的代码——多阶段构建负责瘦身、Compose 负责编排、CMD/ENTRYPOINT 负责定义行为,吃透这三点,”在我机器上能跑”这句话就可以从你的词汇表里删掉了。