Zenn
👽

Dockerイメージ軽量化のアーキテクチャ設計を考える

2025/02/10に公開
7

はじめに

最近、ハッカソンなどで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 を使ったパッケージ管理が、aptyum とはちょっと違う

また、用途に応じて Alpine を適切に選択する必要があります。

https://blog.inductor.me/entry/alpine-not-recommended

3. Slim

  • DebianUbuntu をベースにしつつ、サイズを大幅削減
  • 基本的な機能は残しているので、互換性が高い
  • 公式の言語イメージ(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などのディレクトリを無駄に含めないようにするのがポイントです。

.dockerignore
node_modules/
.git/
*.log

一時ファイルの削除

DockerfileのRUNコマンドで一時ファイルをダウンロードしてインストールする処理がある場合、不要になったファイルをその場で削除することで、コンテナサイズを抑えることができます。

Dockerfile
RUN apt-get update && apt-get install -y \
    curl \
    unzip \
    && rm -rf /var/lib/apt/lists/*

📌 ポイント

  • rm -rf /var/lib/apt/lists/*でパッケージリストを削除

レイヤー数を減らす

Dockerイメージは「レイヤー」の積み重ねで構成されています。
RUNCOPYなどの命令単位でレイヤーが作成されます。
不要なレイヤーを減らすことで、イメージサイズを小さくし、ビルド時間を短縮できます。
NGパターン

Dockerfile
RUN apt-get update
RUN apt-get install -y curl unzip
RUN rm -rf /var/lib/apt/lists/*

これを&&\でコマンドをつなげレイヤー数を減らしていく!

Dockerfile
RUN apt-get update && apt-get install -y curl unzip \
    && rm -rf /var/lib/apt/lists/*

マルチステージビルドを活用する

大きな依存関係がある場合、マルチステージビルドを使うことで実行環境をシンプルにし、イメージサイズを削減できます。

Dockerfile
# ビルド用の環境(サイズ大きめ)
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 --from=builder /app/myapp .
CMD ["./myapp"]

📌 ポイント

  • 最初のFROMでビルドを行い、最終的な実行環境には不要なファイルを含めない
  • 軽量なalpineを使うことで、最小限のイメージを作成可能

ビルドキャッシュを活かすために「頻繁に変更されるもの」は後に記述する

Dockerは、ビルド時に 各レイヤーをキャッシュ し、変更がなければそのまま再利用します。
つまり、変更のないレイヤーはスキップされるため、無駄な処理を省くことができます。
❌ キャッシュが活かせない場合

Dockerfile
# 変更が頻繁に発生するソースコードを先にコピーしてしまう
COPY . /app
RUN apt update && apt install -y git

📌 問題点

  • COPY . /appで毎回ファイルが変更されると、以降のすべての処理がキャッシュされず、毎回実行される
  • 毎回RUN apt update && apt install -y gitを実行することになり、ビルド時間が長くなる

✅キャッシュが活かせる場合

Dockerfile
# 変更の少ない環境構築系のコマンドを先に記述
RUN apt update && apt install -y git

# 頻繁に変更されるソースコードを最後にコピー
COPY ./app

上記の手順を意識すれば軽量なDockerイメージを作成することができます。
実際に、PythonのDockerイメージを例に軽量化をしていきましょう。

🐍 Pythonを使った軽量なDockerイメージを作成していく

以下は何も最適化していない状態のDockerfileです。

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"]

まだ何も施していない状態でビルドすると、、、

terminal
REPOSITORY      TAG       IMAGE ID       CREATED         SIZE
python-test     latest    fc7d24615c26   9 seconds ago   1.05GB

イメージサイズは1.05GBもありますね。
上記のDockerfileは以下が問題点となります。

  • gccbuild-essentialなどのビルドツールが本番環境にも含まれる
  • 不要なレイヤーが増えて、イメージサイズが肥大化

これを以下の点に気をつけて修正していきます!

  • 軽量なベースイメージを選ぶ
  • 不要なファイルをコンテナに含めない
  • レイヤー数を減らす
  • 頻繁に変更されるものは後に記述する
  • マルチステージビルドを活用する

具体的に以下の項目を改善していきましょう!

項目 軽量化前 軽量化後
ベースイメージ python:3.11 python:3.11-slim
レイヤー最適化 なし RUN コマンドをまとめて最適化
不要なファイル あり .dockerignore で排除
キャッシュ活用 なし COPY requirements.txt . を先に記述
マルチステージビルド なし 開発ツールを削除し、最小構成に

修正後のファイルが以下の通りです。

Dockerfile
# 📌 ビルド用のコンテナ
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 --from=builder /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イメージの最適化は、ビルド時間やデプロイ速度の向上、ストレージコストの削減にもつながります。
ぜひ、実際に試して、プロジェクトに適用してみてください!

参考文献

https://zenn.dev/ken3pei/articles/1abbf7d974cf5d

https://blog.shinonome.io/lighter-docker-image/

7

Discussion

ログインするとコメントできます