Dockerマルチステージビルド実践

中級 | 25分 で読める | 2025.01.10

マルチステージビルドとは

マルチステージビルドは、複数のFROMステートメントを使用して、ビルド環境と実行環境を分離する手法です。最終イメージのサイズを大幅に削減できます。

基本構造

# ステージ1: ビルド
FROM node:20 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

# ステージ2: 本番
FROM node:20-slim AS production
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
CMD ["node", "dist/index.js"]

Node.js アプリケーション

Next.js

# Dockerfile
FROM node:20-alpine AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --only=production

FROM node:20-alpine AS builder
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
ENV NEXT_TELEMETRY_DISABLED 1
RUN npm run build

FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV production
ENV NEXT_TELEMETRY_DISABLED 1

RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs

COPY --from=builder /app/public ./public
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static

USER nextjs
EXPOSE 3000
ENV PORT 3000
CMD ["node", "server.js"]
// next.config.js
module.exports = {
  output: 'standalone',
};

Express + TypeScript

# ステージ1: 依存関係インストール
FROM node:20-alpine AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci

# ステージ2: ビルド
FROM node:20-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build

# ステージ3: 本番用依存関係のみ
FROM node:20-alpine AS prod-deps
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production

# ステージ4: 実行
FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV production

RUN addgroup -g 1001 -S nodejs
RUN adduser -S nodejs -u 1001

COPY --from=prod-deps /app/node_modules ./node_modules
COPY --from=builder /app/dist ./dist

USER nodejs
EXPOSE 3000
CMD ["node", "dist/index.js"]

Go アプリケーション

# ステージ1: ビルド
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 -ldflags="-w -s" -o /app/server ./cmd/server

# ステージ2: 最小イメージ
FROM scratch AS runner
COPY --from=builder /app/server /server

# CA証明書(HTTPS通信用)
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/

EXPOSE 8080
ENTRYPOINT ["/server"]

イメージサイズ比較

# ビルド用イメージ: ~800MB
# 最終イメージ(scratch): ~10MB

Rust アプリケーション

# ステージ1: ビルド
FROM rust:1.75 AS builder
WORKDIR /app

# 依存関係のキャッシュ
COPY Cargo.toml Cargo.lock ./
RUN mkdir src && echo "fn main() {}" > src/main.rs
RUN cargo build --release
RUN rm -rf src

# ソースコードのビルド
COPY src ./src
RUN touch src/main.rs
RUN cargo build --release

# ステージ2: 最小イメージ
FROM debian:bookworm-slim AS runner
RUN apt-get update && apt-get install -y ca-certificates && rm -rf /var/lib/apt/lists/*

COPY --from=builder /app/target/release/myapp /usr/local/bin/

EXPOSE 8080
CMD ["myapp"]

Python アプリケーション

# ステージ1: ビルド
FROM python:3.12-slim AS builder
WORKDIR /app

RUN pip install --no-cache-dir poetry

COPY pyproject.toml poetry.lock ./
RUN poetry export -f requirements.txt --output requirements.txt

# ステージ2: 実行
FROM python:3.12-slim AS runner
WORKDIR /app

COPY --from=builder /app/requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

RUN useradd -m -u 1000 appuser
USER appuser

EXPOSE 8000
CMD ["gunicorn", "main:app", "-b", "0.0.0.0:8000"]

ベストプラクティス

1. レイヤーキャッシュの活用

# ❌ 悪い例: 毎回全てコピー
COPY . .
RUN npm install

# ✅ 良い例: 依存関係ファイルを先にコピー
COPY package*.json ./
RUN npm ci
COPY . .

2. .dockerignore の活用

# .dockerignore
node_modules
.git
.env
*.md
Dockerfile
docker-compose.yml
.next
dist
coverage

3. セキュリティ

# 非rootユーザーで実行
RUN addgroup --system --gid 1001 app
RUN adduser --system --uid 1001 --ingroup app app
USER app

# 読み取り専用ファイルシステム
# docker run --read-only myimage

4. ヘルスチェック

HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
  CMD curl -f http://localhost:3000/health || exit 1

テスト用ステージ

FROM node:20-alpine AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci

FROM deps AS test
COPY . .
RUN npm run test

FROM deps AS builder
COPY . .
RUN npm run build

FROM node:20-alpine AS runner
WORKDIR /app
COPY --from=builder /app/dist ./dist
CMD ["node", "dist/index.js"]
# テストのみ実行
docker build --target test .

# 本番ビルド
docker build --target runner .

docker-compose での活用

# docker-compose.yml
version: '3.8'

services:
  app:
    build:
      context: .
      target: runner
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=production

  app-dev:
    build:
      context: .
      target: deps
    ports:
      - "3000:3000"
    volumes:
      - .:/app
      - /app/node_modules
    command: npm run dev

関連記事

まとめ

マルチステージビルドは、本番用イメージのサイズを大幅に削減し、セキュリティを向上させます。ビルド環境と実行環境を分離し、必要なファイルのみを最終イメージに含めましょう。

← 一覧に戻る