Ops視点で見直す「コンテナビルド」の落とし穴とベストプラクティス

2025年8月5日(火)
田中 智明
第11回の今回は、Ops視点から見たコンテナビルドの落とし穴とベストプラクティスを解説します。

はじめに

これまでの連載では、DevOpsの「Dev」側、すなわち開発工程で活用するツールや手法を中心に紹介してきました。第2回から第10回を読んでいただければ、Dev側でどのような作業が必要になるのかを把握できるかと思います。

今回からは「Ops」側、つまりインフラや運用の観点に内容をシフトし、より実践的な内容を解説して行きます。今回のテーマは「コンテナビルドで気を付けること」です。前回でも取り上げた「コンテナビルド」について、Opsの視点でさらに掘り下げて解説して行きます。

アプリケーションを本番環境で安定して動かすためには、コンテナイメージの品質が非常に重要です。本稿ではコンテナビルド時によくある失敗や、押さえておきたいベストプラクティスを紹介します。

コンテナビルドとは

コンテナ技術は、アプリケーションをどこでも同じように動かすための基盤として、現代の開発・運用現場で不可欠な存在となっています。しかし、コンテナイメージの作り方1つで、運用の手間やセキュリティリスク、障害発生時の対応スピードが大きく変わります。

例えば、イメージが大きすぎると配布や起動が遅くなり、不要なパッケージや設定が混入していると脆弱性の温床となります。つまり、コンテナビルドは単なる技術的な作業ではなく、サービス全体の品質や信頼性を左右する重要な工程なのです。

コンテナビルドでよくある失敗例

コンテナビルドは一見シンプルな作業に見えますが、実際には多くの落とし穴があります。ここでは、現場でよく見かける典型的な失敗例を取り上げ、それぞれ何が問題なのかを具体的に解説します。自分のDockerfileやビルドプロセスに思い当たる点がないか、ぜひチェックしてみてください。

キャッシュを活用できていない

Dockerビルドでは、レイヤーキャッシュをうまく活用することで、依存関係のインストールなどの処理を高速化できます。しかし、記述順やCOPYの範囲を工夫しないと毎回キャッシュが無効になり、ビルド時間が大幅に伸びてしまいます。

ビルド時間が伸びれば、CI/CDプロセスにも時間を浪費してしまいます。時間で課金されるような外部のサービスを使用している場合には、無駄なコストが発生することになるでしょう。また、自社運用のCI/CDサービスであっても、効率を考え高速にビルドが完了するのが望ましいです。

悪い例(キャッシュが効かないパターン)
FROM node:18
WORKDIR /app
COPY . .
RUN npm install

この例では、全てのソースを先にCOPYしているため、ソースコードが変更されるたびにCOPYレイヤーのキャッシュが無効化され、その後のnpm installも毎回実行されることになり、依存関係のインストールに時間がかかります。

イメージサイズが大きすぎる

不要なファイルやパッケージを含めてしまい、イメージサイズが肥大化することがあります。開発用のファイルやnode_modules、テストコード、ドキュメントなどの全てのファイルがイメージに含まれることでサイズが大きくなります。

イメージサイズが大きくなればそれだけコンテナイメージのpullに時間がかかりますし、レジストリやローカルのストレージも大きく消費してしまいます。本番運用を考えた場合、デプロイ先のクラスターでpullに時間がかかるのは望ましくないでしょう。また、不要なファイルが含まれていれば、それがセキュリティリスクにも繋がります。

悪い例(開発用ツールやキャッシュを残したまま)
FROM node:18
WORKDIR /app
COPY . .
RUN npm install # node_modulesやキャッシュ、テストコードも全て含まれる

セキュリティリスク

コンテナ内のアプリケーションは非特権ユーザー(一般ユーザー)で実行するのが望ましいです。特権ユーザー(root)で実行されたアプリに脆弱性があった場合、コンテナからホストへの攻撃が成立する可能性があります。詳しくは「コンテナ 権限 昇格」や「コンテナエスケープ」などのキーワードで検索してみてください。多くの事例がヒットします。

悪い例(rootユーザーのまま実行)
FROM python:3.11
WORKDIR /app
COPY . .
RUN pip install -r requirements.txt
CMD ["python", "app.py"] # デフォルトでrootユーザー

ビルドの再現性がない

コンテナのビルドは、適切に設定されていればいつどこで実行しても同じイメージが作成されます。これはDockerfileというコンテナイメージのレシピを元にビルドを自動化しているためです。

それでは、どんなときに再現性のないビルドが実行されるのでしょうか。それはベースイメージにlatestタグが指定されたときや、パッケージバージョンを未指定でインストールしたときです。latestタグやバージョン未指定の場合、実行するタイミングによってはベースイメージやパッケージのバージョンが更新されている可能性があります。

悪い例(latestやバージョン未指定)
FROM ubuntu:latest
RUN apt-get update && apt-get install -y nginx

秘密情報の混入

秘密情報はイメージに含めず、環境変数やシークレット管理サービスを利用しましょう。コンテナ内に埋め込んだり、ビルドの途中でコンテナに流し込むと、コンテナイメージが外部に漏れたときに簡単に秘密情報を抜き取られてしまいます。

docker historydocker saveコマンドでは、各レイヤーで作られたファイルシステムを覗き見ることができます。これを利用して秘密情報を使用したレイヤーを特定できれば、容易に秘密情報を抜き出すことができます。

また、Dockerfileのように広く共有するようなファイルに直書きされていると、Dockerfileが外部に漏れたときに簡単に秘密情報が流出してしまいます。

悪い例(Dockerfileに秘密情報を直書き)
FROM node:18
ENV API_KEY=abcdefg123456

コンテナビルドのベストプラクティス

マルチステージビルドの活用

マルチステージビルドを使うことで、最終イメージには本番に必要なファイルだけを含め、不要なものを排除できます。

なお、秘密情報(APIキーやパスワードなど)はマルチステージビルドであってもイメージに含めるべきではありません。Docker BuildKitのsecretマウント機能や、実行時の環境変数注入を活用しましょう。

FROM golang:1.21 AS builder
WORKDIR /app
COPY . .
RUN go build -o app

FROM debian:stable-slim
WORKDIR /app
COPY --from=builder /app/app .
CMD ["./app"]

必要最小限のベースイメージ選定

AlpineやDistrolessなどの軽量イメージを使うと、イメージサイズと脆弱性のリスクを減らせます。Alpineは不要なパッケージを除外することで数MBの小さなベースイメージを実現しているのが特徴です。ただし、Alpineは標準Cライブラリに通常のLinuxが採用しているglibcではなく、glibcよりも後発で軽量なmuslを採用しています。glibcを使用する場合には注意が必要です。また、システムにインストールされているLinux標準のコマンドもBusyBoxという軽量な1バイナリに置き換えられています。「Linuxのような何か」だと思って触るのが安全でしょう。

Distrolessはシェルやデバッグツール、パッケージマネージャーなどを一切含まず、アプリケーションの実行に必要な最小限のファイルのみで構成されているのが特徴です。攻撃対象となりうるファイルやツールが少ない分堅牢であると言えます。Distrolessでアプリケーションを実行できる場合は、こちらを採用するようにしましょう。

FROM alpine:3.18

ユーザー権限の分離

本番環境ではrootユーザーではなく、専用の非特権ユーザーでアプリケーションを実行しましょう。

FROM python:3.11
WORKDIR /app
COPY . .
RUN pip install -r requirements.txt
RUN useradd -m appuser
USER appuser
CMD ["python", "app.py"]

.dockerignoreの活用

不要なファイルをイメージに含めないよう、.dockerignoreを適切に設定しましょう。

.git
node_modules
test/
README.md

イメージのスキャン

TrivyやSnykなどのOSSツールを活用して、ビルドしたイメージに脆弱性が含まれていないかを自動でチェックしましょう。

trivy image myapp:latest

CIパイプラインで自動スキャンし、脆弱性があればリリースを止める運用が推奨されます。

CI/CDとの連携

コンテナビルドはCI/CDパイプラインに組み込むことで、ビルドの自動化や品質担保が可能になります。イメージのタグ付けにはlatestだけでなく、バージョンやコミットハッシュを活用し、どのイメージがどのリリースに対応しているかを明確にしましょう。

おわりに

コンテナビルドの品質は、その後の運用やセキュリティ、スケーラビリティに大きな影響を与えます。今回紹介したポイントを押さえ、安定した運用基盤を築きましょう。次回は「イメージの配布・管理(レジストリ)」について解説する予定です。

日本仮想化技術株式会社
ソーシャルゲーム業界で10年間インフラエンジニアとして活動し、現在は日本仮想化技術でOpsエンジニアを担当。DevOps支援サービス「かんたんDevOps」では仕組み作りや導入支援、技術調査などを行っている。

連載バックナンバー

システム開発技術解説
第11回

Ops視点で見直す「コンテナビルド」の落とし穴とベストプラクティス

2025/8/5
第11回の今回は、Ops視点から見たコンテナビルドの落とし穴とベストプラクティスを解説します。
システム開発技術解説
第10回

「Docker」を使ってアプリケーションをパッケージングしよう

2025/7/15
第10回の今回は、「Docker」を使ってアプリケーションをパッケージングする方法について、基本からステップバイステップで解説します。
システム開発技術解説
第9回

DevOpsを支えるレビュー運用の基本とGitHub活用法

2025/6/24
第9回の今回は、GitHubを用いたコードレビューの基本と実践方法について解説します。

Think ITメルマガ会員登録受付中

Think ITでは、技術情報が詰まったメールマガジン「Think IT Weekly」の配信サービスを提供しています。メルマガ会員登録を済ませれば、メルマガだけでなく、さまざまな限定特典を入手できるようになります。

Think ITメルマガ会員のサービス内容を見る

他にもこの記事が読まれています