Day 3: Dockerfileの書き方から学ぶ、軽量で堅牢なコンテナイメージの作り方
皆さん、こんにちは!30日集中講座のDay 3へようこそ。
昨日、docker pull
コマンドでDocker Hubから公開されているイメージをダウンロードして利用する方法を学びました。しかし、他人が作ったイメージを使うだけでは、アプリケーションの要件に完全に合わせることはできません。
そこで登場するのが「Dockerfile」です。
Dockerfileは、コンテナイメージを自動的に構築するための命令を記述したテキストファイルです。このファイルを使えば、誰でも同じ手順で、何度でも全く同じコンテナイメージを作成できます。これは、開発環境の再現性を確保する上で最も重要な要素の一つです。
今日は、このDockerfileの書き方をマスターし、自分だけのオリジナルイメージを作成してみましょう。
1. Dockerfileの基本構成とビルド
Dockerfileは、通常、アプリケーションのソースコードと同じディレクトリに配置します。ファイル名はそのまま「Dockerfile」です。
my-app/
├── Dockerfile
├── .dockerignore
├── requirements.txt
├── app.py
└── static/
基本的なDockerfileは、主に以下の3つの要素で構成されます。
- ベースイメージの指定 (
FROM
) - ファイルのコピー (
COPY
やADD
) - コマンドの実行 (
RUN
,CMD
,ENTRYPOINT
)
これらの命令を組み合わせることで、アプリケーションの実行環境をゼロから構築できます。
Dockerイメージのビルド
Dockerfileが完成したら、docker build
コマンドを使ってイメージをビルドします。
# カレントディレクトリのDockerfileを使ってイメージをビルド
docker build -t my-app:1.0 .
-
-t
(--tag
): ビルドするイメージに名前とタグを付けます。 -
.
(ドット): Dockerfileが存在するパスを示し、「ビルドコンテキスト」と呼ばれます。
ビルドコンテキストは、docker build
コマンドを実行するディレクトリとその中のすべてのファイルが対象になります。コンテキストが大きいとビルド時間が長くなるため、.dockerignore
ファイルを使って不要なファイルを除外することが重要です。
.dockerignore
ファイルの活用
.dockerignore
ファイルは、ビルドコンテキストから除外したいファイルやディレクトリを指定するために使います。
# .dockerignoreファイルの例
.git
.env
.env.local
node_modules
*.log
Thumbs.db
このファイルを適切に使うことで、ビルドコンテキストのサイズを劇的に削減できます。例えば、node_modules
ディレクトリを除外するだけで、ビルド時間が短縮し、ネットワークトラフィックが削減される効果が見込めます。.dockerignore
なしで500MBだったビルドコンテキストが、ありでは50MBになるなど、90%の削減も珍しくありません。
2. Dockerfileの主要な命令とベストプラクティス
FROM <image>:<tag>
これがDockerfileの最初の命令で、必ず記述します。どのベースイメージを元にして、新しいイメージを構築するかを指定します。
RUN <command>
とレイヤーキャッシュ
RUN
命令は新しいレイヤーを作成し、コマンドを実行します。Dockerには、レイヤーキャッシュという仕組みがあり、同じ命令が続く場合、前回のビルド結果を再利用してビルドを高速化します。
この仕組みを理解することが、効率的なDockerfileを書く鍵です。
# ❌ この書き方では、コードを変更するたびにpip installが毎回実行される
COPY . .
RUN pip install -r requirements.txt
# ✅ この書き方なら、requirements.txtが変更されない限りキャッシュが利用される
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
頻繁に変更されるファイル(例: ソースコード)を最後にコピーすることで、その手前のレイヤーキャッシュが常に再利用され、ビルド時間が短縮されます。
COPY <src> <dest>
と ADD <src> <dest>
ADD
はCOPY
の機能に加えて、URLからのファイルダウンロードや、圧縮ファイルの自動展開といった特別な機能を持っています。ただし、ADD
の自動展開機能は予期しない動作を引き起こす可能性があり、セキュリティの観点からもCOPY
の使用が強く推奨されます。ADD
は明確にその特殊機能が必要な場合のみ使用しましょう。
EXPOSE <port>
EXPOSE 8000
は、ドキュメント用の命令で、実際にポートを公開するわけではありません。このコンテナがどのポートをリッスンするかを示すだけです。
3. セキュリティとパフォーマンスを考慮した実践テクニック
① マルチステージビルド
ビルドに必要なツールと、最終的に実行するアプリケーションを分けることで、最終イメージサイズを劇的に削減します。
# ビルドステージ: 依存関係の多いGoのSDKイメージ
FROM golang:1.18 AS builder
WORKDIR /go/src/app
COPY . .
RUN CGO_ENABLED=0 go build -o /go/bin/app
# 実行ステージ: 実行バイナリのみをコピーする軽量なAlpineイメージ
FROM alpine:3.15
WORKDIR /app
COPY --from=builder /go/bin/app .
CMD ["./app"]
この例では、GoのSDKイメージは約1.2GBですが、マルチステージビルドによって、最終的なイメージはわずか約15MBにまで軽量化されます。
② 非rootユーザーで実行
デフォルトではrootユーザーでコンテナが実行されますが、セキュリティリスクを高めます。コンテナ内でroot権限を奪取されると、ホストOSにも影響が及ぶ可能性があるため、非rootユーザーで実行することが非常に重要です。
# セキュリティのため非rootユーザーを作成
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
# アプリケーションファイルの所有者を変更
COPY --chown=appuser:appgroup . .
# 非rootユーザーに切り替え
USER appuser
③ 環境変数の活用
環境変数は、アプリケーションの挙動をコンテナイメージの再ビルドなしに変更できるため、非常に便利です。ARG
はビルド時、ENV
は実行時に使用されます。
# 環境に応じた設定の例
ARG NODE_ENV=production
ENV NODE_ENV=$NODE_ENV
ENV PORT=8000
④ Dockerfileの健全性をチェック
hadolint
のようなツールを使って、Dockerfileのベストプラクティスを自動でチェックできます。
# hadolintを使ったDockerfileの静的解析
docker run --rm -i hadolint/hadolint < Dockerfile
さらに、ビルドしたイメージに脆弱性がないかを確認することも重要です。Docker Desktop(有料プラン)やAWS ECRでは、イメージスキャン機能が提供されています。
4. まとめと次へのステップ
今日は、Dockerfileを使って自分だけのコンテナイメージを作成する方法を学びました。
- Dockerfileは、イメージ構築のレシピです。
- ビルドコンテキストやレイヤーキャッシュを理解することが効率的なビルドの鍵です。
- マルチステージビルドや非rootユーザー実行など、本番環境に耐えうる堅牢なイメージを構築するベストプラクティスを学びました。
これで、皆さんは「自分でイメージを作成する」という、Docker活用の次のステップに進むことができました。
明日からは、複数のコンテナを連携させる方法を学びます。単一のアプリケーションだけでなく、Webサーバーとデータベースといった複数のサービスを協調させて動かすことが、より実践的な開発の第一歩となります。
次回の予告
Day 4: ローカル開発環境をDocker Composeで構築しよう
それでは、また明日お会いしましょう!