{T}

Docker 镜像构建优化实战

概述

本文聚焦 Docker 镜像构建的优化技巧,包括多阶段构建策略、.dockerignore 配置、镜像体积压缩、构建速度提升,以及 Docker Compose 高级配置和生产级部署方案。

前置知识

学习目标

  1. 掌握多阶段构建减小镜像体积的方法
  2. 理解 .dockerignore 对构建性能的影响
  3. 熟练配置 Docker Compose 环境变量与网络
  4. 能够排查容器构建和运行中的常见问题

一、多阶段构建优化

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.production
bash
# 方式三:命令行传递
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 安全性与命令实践指南