【ITニュース解説】How to check a Dockerfile for errors before building
2026年11月06日に「Dev.to」が公開したITニュース「How to check a Dockerfile for errors before building」について初心者にもわかりやすく解説しています。
ITニュース概要
Dockerfileは、ビルド時にエラーが出なくても、後でセキュリティや運用上の問題を起こすミスが多い。ベースイメージの固定、危険なスクリプト実行の回避、`apt`の最適化、`USER`の指定など7つのチェックで、安全で効率的なコンテナイメージを作成し、本番環境でのトラブルを防ごう。
ITニュース解説
Dockerは、アプリケーションとその実行環境を一つのパッケージにまとめ、どこでも同じように動作させる「コンテナ」を作るための技術だ。そのコンテナを作るための設計図がDockerfileである。このDockerfileに書かれた命令一つ一つが、コンテナの安全性や安定性に直結するため、非常に重要だ。しかし、多くのDockerfileの間違いは、ビルド時にはエラーとして現れないことが多く、むしろアプリケーションが本番環境で稼働し始めた後、真夜中に予期せぬトラブルとして発覚することがある。例えば、コンテナが不必要な高権限で実行されていたり、最新版に更新されてしまった依存関係によって動作が不安定になったり、未検証のインストーラーが実行されてしまったりするケースだ。この記事では、そのような潜在的な問題をビルド前に見つけ出すための7つの重要なチェックポイントと、その具体的な修正方法について解説する。
一つ目のチェックポイントは、「ピン留めされていないベースイメージ」だ。Dockerfileの最初の行でFROM ubuntuのようにベースとなるイメージを指定する際、バージョンタグを省略すると、Dockerは自動的に:latestタグとして解釈する。この:latestタグは、時間とともに内容が更新されるため、今日ビルドしたイメージと、数日後や数ヶ月後にビルドしたイメージとで、その中身が異なってしまう可能性がある。これは、開発環境やテスト環境では正常に動作したアプリケーションが、本番環境では予期せぬ挙動を示すという「再現性の問題」を引き起こしかねない。この問題を避けるためには、FROM ubuntu:22.04のように、具体的なバージョンタグを明示的に指定すべきだ。これにより、常に同じベースイメージを使ってビルドできるため、環境間の差異によるトラブルを防ぐことができる。
二つ目は、同様に「:latestタグの明示的な使用」だ。FROM node:latestのように、特定のバージョンの代わりに:latestタグを直接指定することも、一つ目の問題と同様に再現性を損なう原因となる。:latestは常に最新の状態を指し、テスト時に使ったイメージと、本番環境にデプロイするイメージが異なる可能性があるため、安定した運用が困難になる。このため、ベースイメージには常に具体的なバージョン番号を指定することが強く推奨される。例えば、node:16-slimのように、使用したいNode.jsのバージョンと、より軽量なイメージを示すタグを組み合わせると良い。
三つ目のチェックポイントは、最も危険な「リモートスクリプトの直接実行」、すなわちcurl https://example.com/install.sh | shのようなコマンドだ。この命令は、インターネット上のURLからスクリプトをダウンロードし、それを直接シェルで実行するというものだ。もし、そのスクリプトがホストされているサーバーが攻撃を受けたり、通信経路が傍受されて悪意のあるコードにすり替えられたりした場合、自身のビルド環境で攻撃者のコードを実行してしまうことになる。これは、ビルド環境そのものや、その結果生成されるイメージ、ひいてはそのイメージがデプロイされる本番環境に、深刻なセキュリティ上の脆弱性を生み出す。このような実行方法は絶対に避けるべきであり、信頼できるパッケージマネージャーを通じてツールをインストールするか、公式リリースされたファイルをダウンロードしてハッシュ値などで検証してから使用すべきだ。
四つ目は、「apt-get updateの単独実行」だ。RUN apt-get updateとRUN apt-get install -y curlのように、apt-get updateとapt-get installのコマンドを別々のRUN命令で実行すると、Dockerのレイヤー構造の特性上、updateによって取得されたパッケージリストのキャッシュが、その後のinstallが実行されるレイヤーとは別に、イメージ内に古い状態で残ってしまう。これはイメージのサイズを不必要に増大させるだけでなく、古いパッケージリストに基づいてインストールが行われるリスクも生じる。これを防ぐためには、RUN apt-get update && apt-get install -y curlのように、二つのコマンドを&&で繋いで一つのRUN命令で実行し、同じレイヤーに含めるべきだ。
五つ目は、「apt-get install後のクリーンアップ不足」である。apt-get installを実行すると、パッケージマネージャーがパッケージリストやキャッシュを/var/lib/apt/listsなどのディレクトリに保存する。これらのファイルは、インストールが完了した後は不要だが、そのままイメージ内に残ってしまうと、イメージサイズが肥大化する原因となる。特に、大量のパッケージをインストールする場合には、この不要なデータがかなりの容量を占めることがある。これを避けるためには、インストールコマンドの最後に&& rm -rf /var/lib/apt/listsのようなクリーンアップコマンドを追加し、不要なファイルを削除することが重要だ。これにより、より軽量で効率的なDockerイメージを作成できる。
六つ目は、「ADD命令の不適切な使用」だ。ADD命令は、ローカルファイルをコンテナにコピーするだけでなく、URLからのファイルダウンロードや、圧縮アーカイブ(.tar.gzなど)の自動展開といった複数の機能を持つ。しかし、単にローカルにあるファイルをコンテナ内のパスにコピーしたいだけであれば、COPY命令を使うべきである。COPYはローカルファイルのコピーに特化しており、より意図が明確で、予期せぬ挙動を防ぐことができるため、セキュリティや可読性の観点からも推奨される。不要な機能を持つADDを使うことで、意図しないファイル展開やURLからのダウンロードが発生するリスクを回避できる。
七つ目は、「USER命令の欠如」だ。DockerfileにUSER命令が指定されていない場合、コンテナはデフォルトでrootユーザーとして実行される。rootユーザーはシステムに対して全ての権限を持つため、もしコンテナ内で悪意のあるコードが実行された場合、コンテナ内部だけでなく、ホストシステム全体に深刻なセキュリティ上の脅威を与える可能性がある。セキュリティを強化するためには、アプリケーションの実行に必要な最小限の権限しか持たない非rootユーザー(例えばnobody)を作成し、そのユーザーでコンテナを実行することが強く推奨される。これにより、攻撃者がコンテナを乗っ取ったとしても、その影響範囲を限定できる。
これらの問題点を抱えたDockerfileの例を見てみよう。FROM ubuntuで始まり、apt-get updateとapt-get installが分離され、クリーンアップもなく、ADD . /appの後に危険なcurl https://example.com/install.sh | shがあり、最後にFROM node:latestとUSER命令がないものだ。このようなDockerfileをDockerfile Linterのようなツールでチェックすると、上で説明した7つの問題点が全て検出される。特に、リモートスクリプトの直接実行は「クリティカル」な問題として扱われ、ビルドを直ちに停止させるべきだと警告される。
これらの問題を修正したDockerfileは、次のように改善される。FROM ubuntu:22.04とバージョンを明記し、RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/listsのように、更新・インストール・クリーンアップを一つのRUN命令にまとめ、COPY . /appでファイルをコピーし、最後にUSER nobodyで非rootユーザーを指定している。リモートスクリプトの直接実行の行は完全に削除され、より安全な方法でツールをインストールするように変更されている。このように修正されたDockerfileは、Dockerfile Linterのチェックをパスし、「0個の発見」という結果になる。
このようなDockerfileの構造的なチェックは、Dockerイメージをビルドする前に常に実行されるべきルーティンに含めることが重要だ。python3 scripts/dockerfile_lint.py check --dockerfile Dockerfileのようにコマンドラインツールとして実行することで、開発者が手作業で見落としがちなミスを自動的に検出し、早期に修正できる。特に、クリティカルな問題が検出された場合にはビルドをブロックする設定にしておくことで、潜在的なリスクを持つイメージが本番環境にデプロイされることを防ぐことができる。また、この種のツールはイメージを実際にビルドしたり、ネットワークからイメージをダウンロードしたりしないため、非常に高速に動作し、コミットごとに実行しても開発フローを妨げない。
ただし、これらのツールには限界もある。Dockerfileの構文やベストプラクティス違反をチェックするものであり、実際に指定されたベースイメージやパッケージが存在するかどうか、あるいはそのイメージや含まれるソフトウェアに既知の脆弱性があるかどうかまでは確認しない。これらは、より高度なセキュリティスキャンツールや、実際にイメージをビルドしてテストする工程で確認すべき点だ。しかし、Dockerfileの構造的なチェックは、より時間とコストのかかる後続のテスト工程に進む前の「安価な初期ゲート」として機能し、開発の初期段階で問題を修正することで、最終的な開発コストを大幅に削減することに貢献する。
安全で効率的なコンテナ開発を進めるためには、Dockerfile作成におけるこれらのベストプラクティスを理解し、適用することが不可欠だ。そして、Dockerfile Linterのようなツールを活用して、これらのチェックを自動化する習慣を身につけることで、セキュリティリスクを最小限に抑え、信頼性の高いアプリケーションを継続的に提供できるだろう。