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="[email protected]"
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 [email protected]: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.dockerfile | Dockerfile 路径,默认 Dockerfile |
build.target | 多阶段构建目标阶段 |
build.args | 构建参数,影响 ARG 指令 |
.dockerignore | 排除不需要的文件,加速构建 |
| 多阶段构建 | 分离构建环境和运行环境,减小镜像体积 |
| 缓存优化 | 合理排列 Dockerfile 指令,利用层缓存 |
扩展阅读
上一章:第 7 章 · 依赖与健康检查 ← | 下一章:第 9 章 · 多环境管理 →