Docker 镜像构建优化实战
概述
本文聚焦 Docker 镜像构建的优化技巧,包括多阶段构建策略、.dockerignore 配置、镜像体积压缩、构建速度提升,以及 Docker Compose 高级配置和生产级部署方案。
前置知识
- Docker 容器化部署前端项目实战
- Nest 项目如何编写 Dockerfile
- Docker 基础命令与镜像分层原理
学习目标
- 掌握多阶段构建减小镜像体积的方法
- 理解 .dockerignore 对构建性能的影响
- 熟练配置 Docker Compose 环境变量与网络
- 能够排查容器构建和运行中的常见问题
一、多阶段构建优化
Nuxt3 应用完整 Dockerfile
dockerfile
# ==================== 构建阶段 ====================
FROM node:18-alpine AS builder
WORKDIR /app
RUN npm config set registry https://registry.npmmirror.com
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
# ==================== 生产阶段 ====================
FROM node:18-alpine AS production
WORKDIR /app
COPY --from=builder /app/.output ./.output
EXPOSE 3000
ENV NODE_ENV=production
CMD ["node", ".output/server/index.mjs"]多阶段构建价值
| 阶段 | 包含内容 | 体积 |
|---|---|---|
| 构建阶段 | 完整 node_modules + 源码 + 开发依赖 | ~800MB |
| 生产阶段 | 仅构建产物(.output) | ~150-250MB |
核心原理:构建阶段的 node_modules 和源码不会进入最终镜像,只有 COPY --from=builder 指定的文件会被保留。
NestJS 应用 Dockerfile
dockerfile
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
FROM node:18-alpine AS production
WORKDIR /app
COPY --from=builder /app/package*.json ./
RUN npm install --omit=dev
COPY --from=builder /app/dist ./dist
EXPOSE 3000
ENV NODE_ENV=production
CMD ["node", "dist/main.js"]NestJS 与 Nuxt3 的差异:NestJS 生产阶段需要 npm install --omit=dev 安装运行时依赖,而 Nuxt3 的 .output 已自带所有依赖。
二、.dockerignore 配置
plaintext
# .dockerignore
node_modules
.nuxt
.output
dist
.git
.github
.DS_Store
*.log
.env*| 排除项 | 原因 |
|---|---|
| node_modules | 容器内重新安装,避免跨平台兼容问题 |
| .nuxt / .output / dist | 构建产物,容器内重新生成 |
| .git | 版本历史不需要进入镜像 |
| .env* | 敏感配置不应写入镜像 |
三、镜像构建与运行
构建命令
bash
# 构建带版本号的镜像
docker build -t my-app:1.0 .
# 查看镜像
docker images | grep my-app
# 强制无缓存构建
docker build --no-cache -t my-app:1.0 .运行与调试
bash
# 运行容器
docker run -d --name app -p 3000:3000 my-app:1.0
# 查看日志
docker logs -f app
# 进入容器调试
docker exec -it app sh
# 查看容器资源占用
docker stats app四、Docker Compose 高级配置
环境变量三种方式
yaml
version: '3.8'
services:
app:
image: my-app:1.0
restart: always
ports:
- "3000:3000"
# 方式一:直接定义
environment:
- NODE_ENV=production
- PORT=3000
# 方式二:引用 .env 文件(推荐)
env_file:
- .env.productionbash
# 方式三:命令行传递
docker run -e BASE_URL=http://api.example.com my-app:1.0完整生产配置
yaml
version: '3.8'
services:
app:
image: my-app:1.0
container_name: my-app-prod
restart: always
ports:
- "3000:3000"
env_file:
- .env.production
networks:
- app-network
healthcheck:
test: ["CMD", "wget", "--spider", "-q", "http://localhost:3000/health"]
interval: 30s
timeout: 10s
retries: 3
networks:
app-network:
driver: bridge常用命令
bash
docker compose up -d # 后台启动
docker compose down # 停止并删除
docker compose ps # 查看状态
docker compose logs -f app # 查看日志
docker compose pull # 拉取最新镜像
docker compose up -d --build # 重新构建并启动五、镜像优化技巧
体积优化
| 策略 | 效果 |
|---|---|
| 使用 alpine 基础镜像 | 从 ~900MB 降至 ~150MB |
| 多阶段构建 | 排除开发依赖和源码 |
| .dockerignore | 减少构建上下文传输 |
| 合并 RUN 指令 | 减少镜像层数 |
| npm ci 代替 npm install | 确定性安装,无缓存 |
构建速度优化
dockerfile
# 利用 Docker 层缓存:先复制依赖文件,再复制源码
COPY package*.json ./
RUN npm ci # 依赖不变时复用缓存
COPY . . # 源码变化不影响依赖层缓存
RUN npm run build包管理工具选择(容器场景)
| 工具 | 容器场景推荐度 | 原因 |
|---|---|---|
| npm | 推荐 | 兼容性好,无软链接问题 |
| yarn | 推荐 | 确定性安装 |
| pnpm | 不推荐 | 软链接机制在容器内可能路径异常 |
常见问题
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 构建失败找不到文件 | .dockerignore 排除了必要文件 | 检查排除规则 |
| 镜像体积过大 | 未使用多阶段构建 | 分离构建和生产阶段 |
| 容器启动即退出 | CMD 命令错误或端口未暴露 | docker logs 排查 |
| 端口冲突 | 宿主机端口被占用 | netstat -tlnp 检查并更换端口 |
| 环境变量未生效 | env_file 路径错误 | 确认路径相对于 docker-compose.yml |
排查流程
bash
docker ps -a # 容器状态
docker logs -f <container> # 日志
docker exec -it <container> sh # 进入调试
docker inspect <container> | grep -A5 Env # 检查环境变量
docker build --no-cache -t app:1.0 . # 排除缓存问题最佳实践
- 始终使用多阶段构建,最终镜像只包含运行时必需文件
- 基础镜像锁定具体版本(
node:18-alpine),避免latest - 利用层缓存:不常变化的层(依赖安装)放前面
- 生产镜像不以 root 运行(参见下一篇安全配置)
- 镜像标签使用 Git SHA 或语义化版本,便于追溯和回滚
延伸阅读
上一篇:Docker 容器化部署前端项目实战 下一篇:Docker 安全性与命令实践指南