Dockerイメージ軽量化のアーキテクチャ設計を考える
はじめに
最近、ハッカソンなどでDockerに触れる機会が増え、Dockerイメージをこだわって作りたいと考え、本記事を作成しようと思いました。
特に、Amazon ECRを使用する中で、Dockerイメージの設計や最適化がパフォーマンスや運用のしやすさに大きく影響することを実感しました。
早速、軽量化する必要性から見ていきましょう。
なぜ、軽量化する必要があるのか?
コンテナイメージが大きいと、以下のようなデメリットがあります。
- ローカルストレージの圧迫
- Docker HubやAWS ECRなどのレジストリにpush/pullする際に時間がかかる
- ビルドに時間がかかる
このように、レジストリからのpullの時間がかかることで、デプロイやスケールアウト時の待ち時間が長くなるといった問題が発生します。
これにより、迅速なデプロイが求められる環境では、パフォーマンスの低下につながるため、コンテナの軽量化は非常に重要です。
どうやって軽量化すればいいの?
では、実際にDockerイメージを軽量化するには、どのような工夫が必要でしょうか?
まずは、ベースとなるイメージを選んでいきましょう。
イメージの選び方
イメージの選び方は、大きく分けて以下の4つあります。
それぞれ特徴を見ていきましょう。
Debian 系(安定感を求めるなら)
Ubuntu
などと同じ Linux ディストリビューションがベースで、安定感抜群です。
- 公式パッケージが豊富で、幅広いアプリに対応
- versionごとに 以下のコードネームが付く
- Bullseye(v11)
- Buster(v10)
- Stretch(v9)
- Jessie(v8)
- 互換性が高い
2. Alpine
「できるだけコンテナを軽くしたい!」という人に人気なのが Alpine。
Dockerの世界では 「軽量イメージの代表格」 です。
- 超コンパクト(5MB以下)で、リソース消費が最小限
- BusyBox をベースにしており、メモリ効率が良い
- 標準の C ライブラリが
musl
のため、一部アプリと互換性に注意
⚠️ 注意点
-
glibc
依存のアプリ(Python, Node.js など)だと動かないことがある -
apk
を使ったパッケージ管理が、apt
やyum
とはちょっと違う
また、用途に応じて Alpine
を適切に選択する必要があります。
3. Slim
-
Debian
やUbuntu
をベースにしつつ、サイズを大幅削減 - 基本的な機能は残しているので、互換性が高い
- 公式の言語イメージ(Python, Node.js など)にも
-slim
が用意されている
⚠️ 注意点
-
slim
版では、一部のツールやライブラリが削除されているので要注意
4. Distroless(最小構成 & セキュリティ重視)
Googleが開発・提供していて、不要なOS部分をバッサリ削った最小構成のイメージ です。
- 言語ランタイムだけを含む超軽量設計
- シェルもパッケージマネージャもないため、セキュリティが超強い
- 公式の
gcr.io/distroless/python3-debian11
などが利用可能
⚠️ 注意点
- シェル (
/bin/sh
) がないので、コンテナ内でbash
したりできない - デバッグが難しく、最初のセットアップに少し手間がかかる
イメージ | 特徴 | 向いているケース |
---|---|---|
Debian | 安定性が高く、パッケージが豊富 | 互換性を重視したい開発環境 |
Alpine | 超軽量でリソース効率が高い | 最小限の環境で動作するアプリ |
Slim | 主要な機能を維持しつつサイズ削減 | 軽量化しつつ互換性も確保したい |
Distroless | OS なしで高セキュリティ | セキュリティ重視の本番環境 |
イメージを選んだら、次のステップに進みましょう。
不要なファイルはコンテナから排除する
次は「本当に必要なファイルだけを残す」作業を行なっていきます。
無駄なファイルがあると、イメージサイズが大きくなり、ビルド時間やデプロイ時間が増加してしまいます。
.dockerignoreを活用する
Dockerでは .dockerignore
を使用すると、不要なファイルをコンテナに含めないようにすることができます。
特に node_modules
や.git
などのディレクトリを無駄に含めないようにするのがポイントです。
node_modules/
.git/
*.log
一時ファイルの削除
DockerfileのRUN
コマンドで一時ファイルをダウンロードしてインストールする処理がある場合、不要になったファイルをその場で削除することで、コンテナサイズを抑えることができます。
RUN apt-get update && apt-get install -y \
curl \
unzip \
&& rm -rf /var/lib/apt/lists/*
📌 ポイント
-
rm -rf /var/lib/apt/lists/*
でパッケージリストを削除
レイヤー数を減らす
Dockerイメージは「レイヤー」の積み重ねで構成されています。
RUN
やCOPY
などの命令単位でレイヤーが作成されます。
不要なレイヤーを減らすことで、イメージサイズを小さくし、ビルド時間を短縮できます。
NGパターン
RUN apt-get update
RUN apt-get install -y curl unzip
RUN rm -rf /var/lib/apt/lists/*
これを&&
や\
でコマンドをつなげレイヤー数を減らしていく!
RUN apt-get update && apt-get install -y curl unzip \
&& rm -rf /var/lib/apt/lists/*
マルチステージビルドを活用する
大きな依存関係がある場合、マルチステージビルドを使うことで実行環境をシンプルにし、イメージサイズを削減できます。
# ビルド用の環境(サイズ大きめ)
FROM golang:1.20 AS builder
WORKDIR /app
COPY . .
RUN go build -o myapp
# 実行環境(サイズ小さめ)
FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR /app
COPY /app/myapp .
CMD ["./myapp"]
📌 ポイント
- 最初の
FROM
でビルドを行い、最終的な実行環境には不要なファイルを含めない - 軽量な
alpine
を使うことで、最小限のイメージを作成可能
ビルドキャッシュを活かすために「頻繁に変更されるもの」は後に記述する
Dockerは、ビルド時に 各レイヤーをキャッシュ し、変更がなければそのまま再利用します。
つまり、変更のないレイヤーはスキップされるため、無駄な処理を省くことができます。
❌ キャッシュが活かせない場合
# 変更が頻繁に発生するソースコードを先にコピーしてしまう
COPY . /app
RUN apt update && apt install -y git
📌 問題点
-
COPY . /app
で毎回ファイルが変更されると、以降のすべての処理がキャッシュされず、毎回実行される - 毎回
RUN apt update && apt install -y git
を実行することになり、ビルド時間が長くなる
✅キャッシュが活かせる場合
# 変更の少ない環境構築系のコマンドを先に記述
RUN apt update && apt install -y git
# 頻繁に変更されるソースコードを最後にコピー
COPY ./app
上記の手順を意識すれば軽量なDockerイメージを作成することができます。
実際に、PythonのDockerイメージを例に軽量化をしていきましょう。
🐍 Pythonを使った軽量なDockerイメージを作成していく
以下は何も最適化していない状態のDockerfileです。
FROM python:3.11
WORKDIR /app
COPY . .
RUN apt-get update
RUN apt-get install -y gcc build-essential
RUN pip install -r requirements.txt
CMD ["python", "app.py"]
まだ何も施していない状態でビルドすると、、、
REPOSITORY TAG IMAGE ID CREATED SIZE
python-test latest fc7d24615c26 9 seconds ago 1.05GB
イメージサイズは1.05GBもありますね。
上記のDockerfileは以下が問題点となります。
-
gcc
やbuild-essential
などのビルドツールが本番環境にも含まれる - 不要なレイヤーが増えて、イメージサイズが肥大化
これを以下の点に気をつけて修正していきます!
- 軽量なベースイメージを選ぶ
- 不要なファイルをコンテナに含めない
- レイヤー数を減らす
- 頻繁に変更されるものは後に記述する
- マルチステージビルドを活用する
具体的に以下の項目を改善していきましょう!
項目 | 軽量化前 | 軽量化後 |
---|---|---|
ベースイメージ | python:3.11 |
python:3.11-slim |
レイヤー最適化 | なし |
RUN コマンドをまとめて最適化 |
不要なファイル | あり |
.dockerignore で排除 |
キャッシュ活用 | なし |
COPY requirements.txt . を先に記述 |
マルチステージビルド | なし | 開発ツールを削除し、最小構成に |
修正後のファイルが以下の通りです。
# 📌 ビルド用のコンテナ
FROM python:3.11-slim AS builder
WORKDIR /app
# 依存関係をインストール(キャッシュを活用)
COPY requirements.txt .
RUN apt-get update && apt-get install -y gcc build-essential \
&& pip install --no-cache-dir -r requirements.txt \
&& rm -rf /var/lib/apt/lists/*
# 📌 実行用の軽量コンテナ
FROM python:3.11-slim
WORKDIR /app
# 依存関係のみをコピー
COPY /usr/local/lib/python3.11/site-packages /usr/local/lib/python3.11/site-packages
# アプリケーションコードをコピー
COPY . .
CMD ["python", "app.py"]
それではイメージを確認してみましょう。
REPOSITORY TAG IMAGE ID CREATED SIZE
python-test latest 74e451bd82bb 8 seconds ago 191MB
191MB、修正前と比べますと約5分の1程度軽量化することができました!
まとめ
Dockerイメージの軽量化について、5つのポイントを解説しました。
✅ 軽量なベースイメージを選ぶ(python:3.11-slim
など)
✅ .dockerignore
を活用し、不要なファイルをコンテナに含めない
✅ レイヤー数を減らす
✅ 頻繁に変更されるものは後に記述し、キャッシュを最大限活用する
✅ マルチステージビルドを活用し、実行環境を最適化する
Dockerイメージの最適化は、ビルド時間やデプロイ速度の向上、ストレージコストの削減にもつながります。
ぜひ、実際に試して、プロジェクトに適用してみてください!
参考文献
Discussion