强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

Docker Compose 完全指南 / 第 8 章 · 构建:build context、Dockerfile 与多阶段构建

第 8 章 · 构建镜像

8.1 build 与 image 的关系

Compose 中有两种获取镜像的方式:

方式说明适用场景
image:使用已有镜像官方镜像、预构建的私有镜像
build:从 Dockerfile 构建自定义应用
两者同时使用构建后打上指定标签自定义镜像名 + 自动构建
services:
  # 方式一:仅用已有镜像
  db:
    image: postgres:16-alpine

  # 方式二:从 Dockerfile 构建
  api:
    build: ./api

  # 方式三:构建 + 自定义镜像名
  web:
    build: ./web
    image: myregistry.io/myapp-web:latest    # 构建后也标记为此名

8.2 build 短语法

services:
  # 最简单:指定构建上下文目录
  app:
    build: .

  # 指定目录 + Dockerfile
  api:
    build:
      context: ./api
      dockerfile: Dockerfile.prod

  # 指定目录 + 自定义 Dockerfile 名
  worker:
    build:
      context: .
      dockerfile: Dockerfile.worker

8.3 build 长语法(完整选项)

services:
  app:
    build:
      # 构建上下文(必填)
      context: ./app

      # Dockerfile 路径(相对于 context)
      dockerfile: Dockerfile

      # Dockerfile 内联(V2.20+,无需单独文件)
      # dockerfile_inline: |
      #   FROM node:20-alpine
      #   COPY . /app
      #   CMD ["node", "app.js"]

      # 构建参数
      args:
        NODE_ENV: production
        APP_VERSION: "${APP_VERSION:-1.0.0}"

      # 镜像标签
      tags:
        - myapp:latest
        - myapp:${APP_VERSION:-1.0.0}
        - registry.example.com/myapp:latest

      # 额外的镜像标签(不影响 Compose 使用的镜像名)
      # tags 会作为额外标签,而 image 指令的值是主标签

      # 目标阶段(多阶段构建)
      target: production

      # 构建缓存来源
      cache_from:
        - myapp:latest
        - type=registry,ref=registry.example.com/myapp:cache

      # 构建缓存导出目标
      cache_to:
        - type=registry,ref=registry.example.com/myapp:cache,mode=max

      # 网络模式(构建时)
      network: host       # 或自定义网络名

      # 不使用缓存
      no_cache: false

      # 拉取策略
      pull: false         # true = 总是拉取基础镜像

      # 额外的构建上下文(V2.20+)
      additional_contexts:
        shared: ./shared-libs
        docs: https://github.com/example/docs.git

      # 隐私模式(敏感文件不进入构建上下文)
      # 通过 .dockerignore 或 SSH mount 实现

      # 平台(多架构构建)
      platforms:
        - linux/amd64
        - linux/arm64

      # 权限(SSH agent 转发等)
      # 需要通过 docker buildx bake 或直接使用 buildx
      # Compose 中使用 SSH 挂载见下文

8.4 Build Context(构建上下文)

构建上下文是 docker build 时发送给 Docker 引擎的文件集合。

上下文的工作原理

┌─────────────── 构建上下文 ────────────────┐
│                                           │
│  ./app/                                   │
│  ├── Dockerfile                           │
│  ├── .dockerignore    ← 排除不需要的文件   │
│  ├── src/                                 │
│  │   ├── main.py                         │
│  │   └── utils.py                        │
│  ├── requirements.txt                     │
│  ├── tests/           ← 被 .dockerignore  │
│  └── __pycache__/     ← 被 .dockerignore  │
│                                           │
└───────────────────────────────────────────┘
         │
         │  构建时发送给 Docker 引擎
         ▼
    ┌────────────┐
    │ Docker 引擎 │
    │ (dockerd)   │
    └────────────┘

.dockerignore 文件

# .dockerignore — 减小构建上下文大小

# 版本控制
.git
.gitignore

# 依赖目录
node_modules
vendor
__pycache__
*.pyc

# 构建产物
dist
build
*.egg-info

# 开发文件
.env
.env.*
*.md
tests/
docs/
.vscode/
.idea/

# Docker 相关
Dockerfile*
docker-compose*.yml
.dockerignore

# 系统文件
.DS_Store
Thumbs.db
*.swp

💡 最佳实践:始终维护 .dockerignore 文件,排除不需要的文件可以显著加快构建速度并减小镜像体积。


8.5 Dockerfile 最佳实践

基本结构

# 基础镜像
FROM python:3.12-slim

# 元数据
LABEL maintainer="dev@example.com"
LABEL version="1.0"

# 环境变量
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1

# 工作目录
WORKDIR /app

# 系统依赖(变化频率低,放前面)
RUN apt-get update && \
    apt-get install -y --no-install-recommends \
        libpq-dev \
        gcc && \
    rm -rf /var/lib/apt/lists/*

# 依赖安装(利用缓存层)
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# 应用代码(变化频率高,放最后)
COPY src/ ./src/

# 非 root 用户
RUN useradd -m appuser
USER appuser

# 暴露端口(文档性质)
EXPOSE 8000

# 启动命令
CMD ["gunicorn", "src.main:app", "--bind", "0.0.0.0:8000"]

层缓存优化

# ❌ 不好的做法:每次代码变化都要重新安装依赖
COPY . .
RUN pip install -r requirements.txt

# ✅ 好的做法:依赖文件先复制,利用缓存
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
层缓存机制:
┌───────────────────┐
│ FROM python:3.12  │  ← 缓存命中(基础镜像没变)
├───────────────────┤
│ RUN apt-get ...   │  ← 缓存命中(Dockerfile 没变)
├───────────────────┤
│ COPY req.txt      │  ← 缓存命中(文件内容没变)
├───────────────────┤
│ RUN pip install   │  ← 缓存命中(上层没变)
├───────────────────┤
│ COPY src/         │  ← ❌ 缓存失效(代码变了)
├───────────────────┤
│ CMD [...]         │  ← 重建
└───────────────────┘

8.6 多阶段构建

多阶段构建可以显著减小镜像体积,将构建环境和运行环境分离。

Go 应用示例

# 阶段一:构建
FROM golang:1.22-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o /app/server ./cmd/server

# 阶段二:运行(仅包含二进制)
FROM alpine:3.20 AS production
RUN apk add --no-cache ca-certificates tzdata
COPY --from=builder /app/server /usr/local/bin/server
USER nobody
EXPOSE 8080
CMD ["server"]
镜像体积对比:
┌──────────────────┬─────────────┐
│ golang:1.22      │ ~800MB      │ ← 构建环境
├──────────────────┼─────────────┤
│ 最终镜像          │ ~15MB       │ ← 仅二进制 + Alpine
└──────────────────┴─────────────┘

Node.js 前端应用

# 阶段一:构建前端资源
FROM node:20-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

# 阶段二:Nginx 服务
FROM nginx:alpine AS production
COPY --from=build /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

Python 应用

# 阶段一:安装依赖
FROM python:3.12-slim AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir --target=/app/deps -r requirements.txt

# 阶段二:运行
FROM python:3.12-slim AS production
WORKDIR /app
COPY --from=builder /app/deps /usr/local/lib/python3.12/site-packages/
COPY src/ ./src/
RUN useradd -m appuser
USER appuser
EXPOSE 8000
CMD ["python", "-m", "uvicorn", "src.main:app", "--host", "0.0.0.0"]

在 Compose 中使用多阶段构建

services:
  # 开发环境 — 使用 dev 阶段
  app:
    build:
      context: ./app
      target: development    # 构建到 dev 阶段
    volumes:
      - ./app/src:/app/src   # 代码热重载

  # 生产环境 — 使用 production 阶段
  # (通过 compose.prod.yaml 覆盖)
  # app:
  #   build:
  #     context: ./app
  #     target: production
# 多阶段 Dockerfile(开发 + 生产)
FROM python:3.12-slim AS base
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

FROM base AS development
RUN pip install --no-cache-dir pytest debugpy
CMD ["python", "-m", "debugpy", "--listen", "0.0.0.0:5678", "-m", "uvicorn", "main:app", "--reload"]

FROM base AS production
COPY src/ ./src/
RUN useradd -m appuser
USER appuser
CMD ["uvicorn", "main:app", "--host", "0.0.0.0"]

8.7 Build Args(构建参数)

构建参数在 docker build 时传入,可在 Dockerfile 中通过 ARG 使用。

Dockerfile 中声明

FROM node:20-alpine

ARG NODE_ENV=production
ARG APP_VERSION

ENV NODE_ENV=${NODE_ENV}

RUN echo "Building version ${APP_VERSION} in ${NODE_ENV} mode"

Compose 中传递

services:
  app:
    build:
      context: ./app
      args:
        NODE_ENV: production
        APP_VERSION: "${APP_VERSION:-1.0.0}"
        # 传递空值会从宿主机继承同名变量
        HTTP_PROXY: ""
# 通过命令行覆盖
APP_VERSION=2.0.0 docker compose build

⚠️ 安全警告ARG 值会记录在镜像历史中(docker history)。不要传递密码、密钥等敏感信息。使用 --mount=type=secret 替代。


8.8 SSH 与 Secret Mounts

SSH Agent 转发

用于构建时需要访问私有 Git 仓库等场景。

# Dockerfile
FROM alpine:3.20
RUN apk add --no-cache git
RUN --mount=type=ssh git clone git@github.com:private/repo.git /app
services:
  app:
    build:
      context: .
      ssh:
        - default           # 使用默认 SSH agent
# 构建前确保 SSH agent 运行
eval $(ssh-agent)
ssh-add ~/.ssh/id_ed25519

# 使用 Compose 构建
docker compose build --ssh default

Secret Mounts(构建时密钥)

# Dockerfile
FROM alpine:3.20
RUN --mount=type=secret,id=npmrc,target=/root/.npmrc \
    npm install
services:
  app:
    build:
      context: .
      secrets:
        - npmrc

secrets:
  npmrc:
    file: .npmrc

💡 --mount=type=secret 不会将密钥写入镜像层,比 COPY + RUN rm 更安全。


8.9 多架构构建

使用 buildx 构建多架构镜像

services:
  app:
    build:
      context: ./app
      platforms:
        - linux/amd64
        - linux/arm64
# 需要创建支持多架构的 builder
docker buildx create --name mybuilder --use
docker buildx inspect --bootstrap

# 构建并推送
docker buildx bake --push

⚠️ 注意:多架构构建比单架构慢很多。开发时建议只构建当前架构。


8.10 构建缓存优化

使用 registry 缓存

services:
  app:
    build:
      context: ./app
      cache_from:
        - type=registry,ref=registry.example.com/myapp:cache
      cache_to:
        - type=registry,ref=registry.example.com/myapp:cache,mode=max

使用本地缓存

services:
  app:
    build:
      context: ./app
      cache_from:
        - myapp:latest          # 从本地镜像缓存

缓存模式

模式说明
mode=min仅缓存最终阶段的层(默认)
mode=max缓存所有阶段的所有层

8.11 Compose 构建命令

# 构建所有服务的镜像
docker compose build

# 构建指定服务
docker compose build web api

# 不使用缓存
docker compose build --no-cache

# 强制拉取最新基础镜像
docker compose build --pull

# 并行构建
docker compose build --parallel

# 构建并启动
docker compose up -d --build

# 只构建不启动
docker compose build

# 查看构建计划(Bake 格式)
docker compose build --print

8.12 完整项目示例

项目结构

myapp/
├── docker-compose.yaml       # 或 compose.yaml
├── .env
├── backend/
│   ├── Dockerfile
│   ├── .dockerignore
│   ├── requirements.txt
│   └── src/
├── frontend/
│   ├── Dockerfile
│   ├── .dockerignore
│   ├── package.json
│   └── src/
└── nginx/
    └── nginx.conf

compose.yaml

services:
  backend:
    build:
      context: ./backend
      target: ${BUILD_TARGET:-development}
      args:
        PYTHON_VERSION: "3.12"
    image: myapp-backend:${APP_VERSION:-latest}
    volumes:
      - ./backend/src:/app/src   # 开发模式:代码同步
    environment:
      DATABASE_URL: postgresql://postgres:secret@db:5432/myapp

  frontend:
    build:
      context: ./frontend
      target: ${BUILD_TARGET:-development}
    image: myapp-frontend:${APP_VERSION:-latest}
    volumes:
      - ./frontend/src:/app/src

  nginx:
    build:
      context: ./nginx
    ports:
      - "80:80"
    depends_on:
      - backend
      - frontend

  db:
    image: postgres:16-alpine
    volumes:
      - pgdata:/var/lib/postgresql/data
    environment:
      POSTGRES_PASSWORD: secret

volumes:
  pgdata:

8.13 小结

概念说明
build.context构建上下文目录,所有文件发送给 Docker 引擎
build.dockerfileDockerfile 路径,默认 Dockerfile
build.target多阶段构建目标阶段
build.args构建参数,影响 ARG 指令
.dockerignore排除不需要的文件,加速构建
多阶段构建分离构建环境和运行环境,减小镜像体积
缓存优化合理排列 Dockerfile 指令,利用层缓存

扩展阅读


上一章:第 7 章 · 依赖与健康检查 ← | 下一章:第 9 章 · 多环境管理 →