序論:Dockerfileの重要性

Dockerシリーズ第3編では、コンテナイメージを作成する核心要素であるDockerfileについて深く解説します。Dockerfileはアプリケーションをコンテナ化するための設計図であり、これをうまく作成することが効率的で安全なコンテナ運用の第一歩です。

この記事では、Dockerfileの基本概念から主要コマンド、マルチステージビルド、ビルドキャッシュ活用、そしてセキュリティを考慮したベストプラクティスまで詳しく解説します。

1. Dockerfileとは?

1.1 Dockerfileの定義

DockerfileはDockerイメージをビルドするためのテキストファイルです。このファイルにはベースイメージの選択からアプリケーションのインストール、環境設定、実行コマンドまで、イメージ作成に必要なすべてのコマンドが順次定義されています。

Dockerfileの主な特徴:

  • 再現可能性:同一のDockerfileでいつでもどこでも同じイメージをビルドできます
  • バージョン管理:テキストファイルなのでGitなどのバージョン管理システムで変更履歴を追跡できます
  • 自動化:CI/CDパイプラインで自動的にイメージをビルドしてデプロイできます
  • ドキュメント化:Dockerfile自体がアプリケーションの実行環境をドキュメント化する役割を果たします

1.2 基本構造

シンプルなNode.jsアプリケーションのためのDockerfile例:

# ベースイメージ指定
FROM node:20-alpine

# 作業ディレクトリ設定
WORKDIR /app

# パッケージファイルコピーと依存関係インストール
COPY package*.json ./
RUN npm install

# ソースコードコピー
COPY . .

# 環境変数設定
ENV NODE_ENV=production

# ポート公開
EXPOSE 3000

# 実行コマンド
CMD ["node", "server.js"]

2. Dockerfile主要コマンド

2.1 FROM - ベースイメージ指定

FROMはすべてのDockerfileの最初のコマンドで、新しいイメージの基盤となるベースイメージを指定します。

# 基本形式
FROM <イメージ>:<タグ>

# 例
FROM ubuntu:22.04
FROM python:3.11-slim
FROM node:20-alpine
FROM scratch  # 空のイメージから開始

ベースイメージ選択時の考慮事項:

  • Alpineイメージ:非常に小さいサイズ(約5MB)、セキュリティ高い、一部互換性の問題がある可能性
  • Slimイメージ:不要なパッケージが削除された軽量バージョン
  • 公式イメージ:Docker Hubで検証された公式イメージの使用を推奨

2.2 RUN - コマンド実行

RUNはイメージビルド過程でコマンドを実行し、その結果を新しいレイヤーとして保存します。

# Shell形式
RUN apt-get update && apt-get install -y curl

# Exec形式
RUN ["apt-get", "update"]
RUN ["apt-get", "install", "-y", "curl"]

# 複数のコマンドを1つのRUNで結合(レイヤー最小化)
RUN apt-get update \
    && apt-get install -y \
        curl \
        vim \
        git \
    && rm -rf /var/lib/apt/lists/*

2.3 COPY vs ADD - ファイルコピー

COPYADDはどちらもファイルをイメージにコピーしますが、用途が異なります。

# COPY - ローカルファイル/ディレクトリをイメージにコピー
COPY src/ /app/src/
COPY package.json package-lock.json ./

# ADD - COPY機能 + 追加機能
# 1. URLからファイルダウンロード
ADD https://example.com/file.tar.gz /tmp/

# 2. 圧縮ファイル自動解凍(tar、gzip、bzip2、xz)
ADD archive.tar.gz /app/

推奨事項:ほとんどの場合COPYを使用してください。ADDはURLダウンロードや自動解凍が必要な場合のみ使用します。

2.4 WORKDIR - 作業ディレクトリ設定

WORKDIRは以降のRUN、CMD、ENTRYPOINT、COPY、ADDコマンドが実行される作業ディレクトリを設定します。

# 作業ディレクトリ設定
WORKDIR /app

# 存在しなければ自動作成される
WORKDIR /app/src

# 相対パスも可能(前のWORKDIR基準)
WORKDIR subdir  # /app/src/subdir

2.5 ENV - 環境変数設定

ENVはコンテナ実行時に使用する環境変数を設定します。

# 単一環境変数
ENV NODE_ENV=production

# 複数環境変数(1行)
ENV NODE_ENV=production PORT=3000

# 複数環境変数(複数行)
ENV NODE_ENV=production \
    PORT=3000 \
    DB_HOST=localhost

2.6 EXPOSE - ポートドキュメント化

EXPOSEはコンテナが使用するポートをドキュメント化します。実際のポートバインディングはdocker run -pで行います。

# 単一ポート
EXPOSE 3000

# 複数ポート
EXPOSE 80 443

# UDPポート
EXPOSE 53/udp

2.7 CMD vs ENTRYPOINT - 実行コマンド

CMDENTRYPOINTはコンテナ起動時に実行するコマンドを定義します。

# CMD - デフォルト実行コマンド(docker runで上書き可能)
CMD ["node", "server.js"]
CMD ["npm", "start"]

# ENTRYPOINT - 固定実行コマンド
ENTRYPOINT ["python", "app.py"]

# CMDとENTRYPOINTの組み合わせ
ENTRYPOINT ["python"]
CMD ["app.py"]  # docker run image other.pyで変更可能
コマンド 役割 上書き
CMD デフォルトコマンド/引数 docker run引数で上書き可能
ENTRYPOINT 固定実行ファイル --entrypointオプションでのみ変更可能

2.8 その他の有用なコマンド

# ARG - ビルド時変数(イメージに保存されない)
ARG VERSION=1.0
ARG BUILD_DATE

# LABEL - メタデータ追加
LABEL maintainer="dev@example.com"
LABEL version="1.0"
LABEL description="My application"

# USER - 実行ユーザー変更
USER node
USER 1001:1001

# VOLUME - ボリュームマウントポイント宣言
VOLUME /data
VOLUME ["/var/log", "/var/db"]

# HEALTHCHECK - コンテナ状態確認
HEALTHCHECK --interval=30s --timeout=3s --retries=3 \
    CMD curl -f http://localhost:3000/health || exit 1

3. マルチステージビルド

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

マルチステージビルドは1つのDockerfileで複数のステージ(stage)を定義し、ビルドに必要なツールと最終実行に必要なファイルを分離する技法です。これにより最終イメージサイズを大幅に削減できます。

3.2 実践例:Goアプリケーション

# ビルドステージ
FROM golang:1.21 AS builder

WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download

COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o main .

# 実行ステージ
FROM alpine:latest

RUN apk --no-cache add ca-certificates
WORKDIR /root/

# ビルドステージからバイナリのみコピー
COPY --from=builder /app/main .

CMD ["./main"]

3.3 実践例:Node.jsアプリケーション

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

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

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

# 本番依存関係のみコピー
COPY --from=deps /app/node_modules ./node_modules
# ビルド成果物のみコピー
COPY --from=builder /app/dist ./dist
COPY package.json ./

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

3.4 マルチステージビルドの利点

  • イメージサイズ削減:ビルドツールなしで実行に必要なファイルのみ含む
  • セキュリティ強化:ビルド時に使用されたソースコード、認証情報などが最終イメージに含まれない
  • ビルドキャッシュ活用:各ステージを独立してキャッシュできる

4. .dockerignoreファイル

4.1 .dockerignoreとは?

.dockerignoreファイルはDockerビルドコンテキストから除外するファイルとディレクトリを指定します。.gitignoreと類似した文法を使用します。

4.2 .dockerignore例

# Git関連
.git
.gitignore

# Node.js
node_modules
npm-debug.log

# ビルド成果物(マルチステージでない場合)
dist
build

# 環境設定
.env
.env.local
*.env

# IDE
.vscode
.idea
*.swp

# テスト
coverage
__tests__
*.test.js

# ドキュメント
README.md
docs

# Docker関連
Dockerfile*
docker-compose*
.docker

4.3 .dockerignoreの重要性

  • ビルド速度向上:不要なファイルがビルドコンテキストに含まれないため転送時間短縮
  • イメージサイズ削減:COPYコマンド使用時に不要なファイルのコピーを防止
  • セキュリティ.env、証明書などの機密ファイルがイメージに含まれるのを防止

5. イメージビルド(docker build)

5.1 基本ビルドコマンド

# 現在ディレクトリのDockerfileでビルド
docker build -t myapp:1.0 .

# 別のDockerfile指定
docker build -f Dockerfile.prod -t myapp:prod .

# ビルド引数渡し
docker build --build-arg VERSION=2.0 -t myapp:2.0 .

# ビルドキャッシュ無視
docker build --no-cache -t myapp:latest .

# 特定ステージまでのみビルド
docker build --target builder -t myapp:builder .

5.2 ビルドコンテキスト

ビルドコンテキストはdocker buildコマンドに渡されるファイルとディレクトリの集合です。最後の引数(`.`)がビルドコンテキストパスを指定します。

# 現在ディレクトリをビルドコンテキストとして使用
docker build -t myapp .

# 特定ディレクトリをビルドコンテキストとして使用
docker build -t myapp /path/to/context

# Gitリポジトリから直接ビルド
docker build -t myapp https://github.com/user/repo.git

# stdinからDockerfile読み込み
docker build -t myapp - < Dockerfile

6. ビルドキャッシュ活用

6.1 キャッシュ動作原理

Dockerはビルド時に各コマンドの結果をレイヤーとしてキャッシュします。以前のビルドと同一のコマンドはキャッシュされたレイヤーを再利用します。

キャッシュが無効化される条件:

  • コマンドが変更された場合
  • COPY/ADDのソースファイルが変更された場合
  • 上位レイヤーのキャッシュが無効化された場合

6.2 キャッシュ効率を高めるヒント

# 悪い例:ソースコード変更時にnpm installも再実行される
COPY . .
RUN npm install

# 良い例:package.jsonが変更されなければキャッシュを使用
COPY package*.json ./
RUN npm install
COPY . .

頻繁に変更されるレイヤーはDockerfileの下部に配置してキャッシュ効率を高めます。

7. イメージタグ付けとバージョン管理

7.1 タグ戦略

# バージョンタグ
docker build -t myapp:1.0.0 .
docker build -t myapp:1.0 .
docker build -t myapp:1 .

# 環境別タグ
docker build -t myapp:prod .
docker build -t myapp:staging .
docker build -t myapp:dev .

# Gitコミットハッシュタグ
docker build -t myapp:$(git rev-parse --short HEAD) .

# 日付タグ
docker build -t myapp:$(date +%Y%m%d) .

7.2 タグ追加と変更

# 既存イメージに新しいタグを追加
docker tag myapp:1.0.0 myapp:latest
docker tag myapp:1.0.0 registry.example.com/myapp:1.0.0

# レジストリにプッシュ
docker push registry.example.com/myapp:1.0.0

8. Dockerfileベストプラクティス

8.1 レイヤー最小化

# 悪い例:不要なレイヤー生成
RUN apt-get update
RUN apt-get install -y curl
RUN apt-get install -y vim
RUN rm -rf /var/lib/apt/lists/*

# 良い例:1つのRUNで結合
RUN apt-get update \
    && apt-get install -y curl vim \
    && rm -rf /var/lib/apt/lists/*

8.2 セキュリティ考慮事項

# 1. 非rootユーザーで実行
FROM node:20-alpine
RUN addgroup -g 1001 -S nodejs \
    && adduser -S nodejs -u 1001
USER nodejs

# 2. 信頼できるベースイメージを使用
FROM node:20-alpine@sha256:abc123...

# 3. 不要なパッケージのインストール禁止
RUN apt-get install --no-install-recommends -y curl

# 4. 機密情報はENVではなく実行時に渡す
# 悪い例
ENV DATABASE_PASSWORD=secret123

# 良い例:docker run -e DATABASE_PASSWORD=xxx を使用

# 5. COPY の代わりに特定ファイルのみコピー
COPY src/ /app/src/
COPY package.json /app/

8.3 イメージサイズ最適化

  • 可能な限り小さいベースイメージを使用(Alpine、distroless)
  • マルチステージビルド活用
  • 不要なファイル削除(キャッシュ、一時ファイル)
  • .dockerignore積極活用

8.4 完成したDockerfile例

# 本番用Node.jsアプリケーションDockerfile
# マルチステージビルドとセキュリティベストプラクティス適用

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

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

# 3. 本番ステージ
FROM node:20-alpine AS runner
LABEL maintainer="dev@example.com"
LABEL version="1.0"

WORKDIR /app
ENV NODE_ENV=production

# 非rootユーザー作成
RUN addgroup -g 1001 -S nodejs \
    && adduser -S nodejs -u 1001

# 必要なファイルのみコピー
COPY --from=deps /app/node_modules ./node_modules
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/package.json ./

# 所有権変更とユーザー切り替え
RUN chown -R nodejs:nodejs /app
USER nodejs

# ヘルスチェック設定
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
    CMD wget --no-verbose --tries=1 --spider http://localhost:3000/health || exit 1

EXPOSE 3000
CMD ["node", "dist/server.js"]

結論

Dockerfile作成はコンテナ化の核心技術です。この記事で解説した内容をまとめると:

  • 基本コマンド理解:FROM、RUN、COPY、WORKDIR、ENV、EXPOSE、CMD、ENTRYPOINTの役割と使用法
  • マルチステージビルド:ビルドと実行環境を分離してイメージサイズを最小化
  • .dockerignore:不要なファイルをビルドから除外
  • ビルドキャッシュ:レイヤー順序を最適化してビルド速度向上
  • ベストプラクティス:セキュリティ、サイズ、保守性を考慮したDockerfile作成

次の第4編ではDocker Composeを使用して複数のコンテナを効率的に管理する方法を学びます。