Dockerfile最適化とは
Dockerfile最適化は、イメージサイズの削減とビルド時間の短縮を目指します。
最適化のメリット
- 📦 デプロイ時間の短縮
- 💰 ストレージコスト削減
- 🚀 コンテナ起動時間の短縮
- 🔒 攻撃面の縮小(セキュリティ向上)
- 🌐 ネットワーク転送量の削減
ベースイメージの選択
Alpine Linuxの使用
# ❌ 大きいイメージ(~900MB)
FROM node:18
# ✅ Alpine版(~170MB)
FROM node:18-alpine
# ✅ さらに小さい slim版(~250MB)
FROM node:18-slimサイズ比較
- node:18: ~900MB
- node:18-slim: ~250MB
- node:18-alpine: ~170MB
distrolessイメージ
# Google製の最小限イメージ
FROM gcr.io/distroless/nodejs:18
# シェルすら含まない(セキュリティ向上)
# デバッグは難しくなるマルチステージビルド
基本的なマルチステージ
# ビルドステージ
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# 本番ステージ
FROM node:18-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY package*.json ./
EXPOSE 3000
CMD ["node", "dist/index.js"]効果
Before: 1.2GB(ビルドツール含む)
After: 180MB(実行に必要なもののみ)Python マルチステージ例
# ビルドステージ
FROM python:3.11-slim AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --user --no-cache-dir -r requirements.txt
# 本番ステージ
FROM python:3.11-slim
WORKDIR /app
COPY --from=builder /root/.local /root/.local
COPY . .
ENV PATH=/root/.local/bin:$PATH
CMD ["python", "app.py"]レイヤーキャッシュの活用
変更頻度の低い順に配置
# ❌ 悪い例(毎回フルビルド)
FROM node:18-alpine
WORKDIR /app
COPY . .
RUN npm install
RUN npm run build
# ✅ 良い例(依存関係をキャッシュ)
FROM node:18-alpine
WORKDIR /app
# 依存関係ファイルのみコピー(変更少ない)
COPY package*.json ./
RUN npm ci
# ソースコードをコピー(変更多い)
COPY . .
RUN npm run buildキャッシュが効く理由
1回目のビルド:
1. COPY package*.json → 実行
2. RUN npm ci → 実行
3. COPY . → 実行
4. RUN npm run build → 実行
2回目(ソースのみ変更):
1. COPY package*.json → キャッシュ使用
2. RUN npm ci → キャッシュ使用
3. COPY . → 実行
4. RUN npm run build → 実行不要なファイルの除外
.dockerignoreの活用
# .dockerignore
node_modules
npm-debug.log
.git
.gitignore
.env
.env.local
README.md
.vscode
.idea
__pycache__
*.pyc
*.pyo
*.log
.DS_Store
dist
build
.cache
coverage
.pytest_cache
# ドキュメント
*.md
!README.md
# テストファイル
test/
tests/
__tests__/
*.test.js
*.spec.js効果
- COPY時の転送データ削減
- イメージサイズ削減
- ビルド時間短縮
RUNコマンドの最適化
複数コマンドの結合
# ❌ 悪い例(レイヤーが増える)
RUN apt-get update
RUN apt-get install -y curl
RUN apt-get install -y vim
RUN apt-get clean
# ✅ 良い例(1レイヤー)
RUN apt-get update && \
apt-get install -y \
curl \
vim && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*キャッシュの削除
# Alpine Linux
RUN apk add --no-cache python3 py3-pip
# Debian/Ubuntu
RUN apt-get update && \
apt-get install -y python3 && \
rm -rf /var/lib/apt/lists/*
# Python pip
RUN pip install --no-cache-dir -r requirements.txt
# npm
RUN npm ci --only=production && \
npm cache clean --forceセキュリティベストプラクティス
非rootユーザーで実行
FROM node:18-alpine
WORKDIR /app
# ユーザー作成
RUN addgroup -g 1001 appgroup && \
adduser -D -u 1001 -G appgroup appuser
# ファイルコピー
COPY --chown=appuser:appgroup package*.json ./
RUN npm ci
COPY --chown=appuser:appgroup . .
# ユーザー切り替え
USER appuser
EXPOSE 3000
CMD ["node", "index.js"]秘密情報の扱い
# ❌ 悪い例(イメージに残る)
RUN echo "API_KEY=secret123" > .env
# ✅ 良い例(ビルド引数)
ARG API_KEY
RUN configure --api-key=$API_KEY
# ✅ または実行時に環境変数で渡す
ENV API_KEY=${API_KEY}
# Docker Secretsを使う(Swarm/Compose)
# docker run -e API_KEY="$API_KEY" myappビルド引数と環境変数
ARG vs ENV
# ARG: ビルド時のみ有効
ARG NODE_ENV=production
RUN if [ "$NODE_ENV" = "production" ]; then \
npm ci --only=production; \
else \
npm ci; \
fi
# ENV: 実行時も有効
ENV NODE_ENV=production
ENV PORT=3000
# 両方使う
ARG VERSION=1.0.0
ENV APP_VERSION=$VERSION条件付きビルド
ARG BUILD_ENV=production
FROM node:18-alpine AS base
WORKDIR /app
# 開発用ステージ
FROM base AS development
RUN npm install
COPY . .
CMD ["npm", "run", "dev"]
# 本番用ステージ
FROM base AS production
RUN npm ci --only=production
COPY . .
RUN npm run build
CMD ["npm", "start"]
# 選択
FROM ${BUILD_ENV} AS finalヘルスチェック
HEALTHCHECKの設定
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
# ヘルスチェック
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD node healthcheck.js || exit 1
EXPOSE 3000
CMD ["node", "index.js"]healthcheck.jsの例
// healthcheck.js
const http = require('http');
const options = {
host: 'localhost',
port: 3000,
path: '/health',
timeout: 2000
};
const request = http.request(options, (res) => {
if (res.statusCode === 200) {
process.exit(0);
} else {
process.exit(1);
}
});
request.on('error', () => process.exit(1));
request.end();実践的なDockerfile例
Node.js アプリケーション
FROM node:18-alpine AS builder
WORKDIR /app
# 依存関係インストール
COPY package*.json ./
RUN npm ci
# ビルド
COPY . .
RUN npm run build && \
npm prune --production
# 本番イメージ
FROM node:18-alpine
WORKDIR /app
# 非rootユーザー
RUN addgroup -g 1001 nodejs && \
adduser -D -u 1001 -G nodejs nodejs
# ビルド成果物コピー
COPY --from=builder --chown=nodejs:nodejs /app/dist ./dist
COPY --from=builder --chown=nodejs:nodejs /app/node_modules ./node_modules
COPY --from=builder --chown=nodejs:nodejs /app/package.json ./
USER nodejs
EXPOSE 3000
HEALTHCHECK --interval=30s --timeout=3s \
CMD node -e "require('http').get('http://localhost:3000/health', (r) => process.exit(r.statusCode === 200 ? 0 : 1))"
CMD ["node", "dist/index.js"]Python Flask アプリケーション
FROM python:3.11-slim AS builder
WORKDIR /app
# 依存関係インストール
COPY requirements.txt .
RUN pip install --user --no-cache-dir -r requirements.txt
# 本番イメージ
FROM python:3.11-slim
WORKDIR /app
# 非rootユーザー
RUN useradd -m -u 1001 appuser
# Pythonパッケージコピー
COPY --from=builder --chown=appuser:appuser /root/.local /home/appuser/.local
COPY --chown=appuser:appuser . .
USER appuser
ENV PATH=/home/appuser/.local/bin:$PATH
ENV PYTHONUNBUFFERED=1
EXPOSE 5000
HEALTHCHECK --interval=30s --timeout=3s \
CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:5000/health')"
CMD ["gunicorn", "-b", "0.0.0.0:5000", "app:app"]ビルド時間の測定
ビルド時間の確認
# 通常ビルド
time docker build -t myapp .
# キャッシュなしビルド
time docker build --no-cache -t myapp .
# BuildKitで詳細表示
DOCKER_BUILDKIT=1 docker build -t myapp .イメージサイズの確認
# イメージ一覧
docker images
# 詳細表示
docker history myapp:latest
# レイヤーごとのサイズ
docker history --no-trunc myapp:latestBuildKitの活用
BuildKit有効化
# 環境変数で有効化
export DOCKER_BUILDKIT=1
# または docker-compose.yml で
version: '3.8'
services:
web:
build:
context: .
dockerfile: Dockerfile
environment:
- DOCKER_BUILDKIT=1BuildKitの機能
# 並列ビルド
DOCKER_BUILDKIT=1 docker build --build-arg BUILDKIT_INLINE_CACHE=1 -t myapp .
# Secretsマウント
# syntax=docker/dockerfile:1
FROM alpine
RUN --mount=type=secret,id=mysecret \
cat /run/secrets/mysecret
# docker build --secret id=mysecret,src=secret.txt .
# キャッシュマウント
RUN --mount=type=cache,target=/root/.cache/pip \
pip install -r requirements.txtまとめ
Dockerfile最適化でイメージサイズを削減し、ビルドを高速化できます。
重要ポイント
- Alpine/Slimイメージを使用
- マルチステージビルドで分離
- レイヤーキャッシュを活用
- .dockerignoreで不要ファイル除外
- 非rootユーザーで実行
チェックリスト
- [ ] ベースイメージは最小限か
- [ ] マルチステージビルドを使っているか
- [ ] .dockerignoreを設定したか
- [ ] RUNコマンドを結合したか
- [ ] キャッシュを削除したか
- [ ] 非rootユーザーで実行するか
- [ ] ヘルスチェックを設定したか