Dockerイメージの安全性を高める10のセキュリティハック
はじめに
本記事は、Snykの『10 Docker Image Security Best Practices』という記事を参考にし、学習の一環としてまとめたものです。
記事の内容をより分かりやすく整理し、個人的な考察や追加情報も加えています。
読者の皆様の学習の一助となれば幸いです。
Dockerのセキュリティとは
Snykの記事では、Dockerのセキュリティとは、Dockerコンテナのビルド
、ランタイム
、オーケストレーションに関する側面
として説明しています。
これには、Dockerベースイメージのセキュリティ対策、ユーザー権限の管理、Dockerデーモンの設定、コンテナのCPU制御など、ランタイムにおけるセキュリティ対策が含まれます。
さらに、大規模なDockerコンテナのオーケストレーションに関する課題にも対応する必要があります。
では実際にどのように対応していくのか、10項目のベストプラクティスを通して、具体的な対策を見ていきます!
Dockerのセキュリティに関する10項目のベストプラクティス
1. ベースイメージの最小化
Dockerコンテナの一般的なセキュリティ課題の一つに、イメージサイズの肥大化
があります。
多くのプロジェクトでは、FROM node
など一般的なベースイメージをそのまま使用して開発を始めることが多いですが、その際には注意が必要です。
例えば、node
イメージは、内部的にDebian Bookworm や Alpine をベースにしたバリアントが提供されており、選択するベースイメージによってサイズや含まれるライブラリが大きく異なります。
もし、プロジェクトで一般的なシステムライブラリやユーティリティが不要であれば、フルバージョンのOSをベースにするのは避けるべきです。一方、node:21-alpine
などの Alpineベースのイメージは、軽量で余分なパッケージが含まれていないため、よりセキュアな運用が可能です。
Dockerの公式セキュリティアドバイザリ(2024年)によると、最近のDocker関連ツール(runc, BuildKit, Moby など)において複数の脆弱性が報告されています。これには、特権昇格やリモートコード実行につながる深刻な問題が含まれています。
また、Docker Hub上の公式イメージであっても脆弱性がゼロとは限らず、常に最新のスキャン結果を確認する必要があります。例えば、Node.jsの最新公式イメージでは、node:latest
にもいくつかのCVE(Common Vulnerabilities and Exposures)が含まれていることが確認できます。
InfoQの記事 によると、2021年の調査では、約400万のDocker Hubイメージのうち、51%以上に悪用可能な脆弱性が含まれていることが報告されています。
IoT OT Security Newsの記事では、2023年の調査によると、数百種類の一般的なコンテナイメージに、高リスクの脆弱性が多数含まれていることが明らかになりました。特に、悪用手法がすでに公開されている脆弱性も含まれており、一部では実際に攻撃へと発展したケースも報告されています。
以上の調査からわかるように、多くのDocker Hubイメージは、そのまま使用すると既知の脆弱性を含んでおり、セキュリティリスクが高いことが伺えます。
プロジェクトの実行に必要なシステムツール
とライブラリ
のみをバンドルする最小限のイメージを使うことを優先することで、攻撃者の攻撃対象領域を最小限に抑え、安全なOSを確実に準備することが可能です。
2. 最小特権ユーザー
デフォルトのDockerfileでは、USER
を明示的に指定しない場合、コンテナ rootユーザー で実行されます。
しかし、多くのアプリケーションでは、root
権限を必要とするケースはほとんどなく、これがセキュリティ上のリスクとなる可能性があります。
なぜ、rootでの実行が危険なのか?
-
ホストへの影響
コンテナがホストの名前空間にマップされると、コンテナのrootユーザーがホストシステムのrootユーザーと同じ権限を持つ可能性があります。 -
攻撃対象の拡大
コンテナ上のアプリケーションが脆弱な場合、root権限のまま実行すると 特権昇格が容易になり、攻撃の被害が拡大します。 -
マルウェア感染のリスク
rootユーザーでの実行は、攻撃者にとって都合がよく、マルウェアが悪用しやすくなります。
最小権限での実行
セキュリティリスクを抑えるためには、コンテナ内のアプリケーションを最小限の特権で実行することが重要です。
そのため、専用のユーザーとグループを作成し、USER
ディレクティブを使用してroot権限なしで動作するように設定しましょう。
以下のDockerfileを最小限の特権で実行できる状態にしてみましょう。
USERを指定しない場合(危険な例)
FROM ubuntu:latest
# アプリケーション用のディレクトリを作成
RUN mkdir /app
# 作業ディレクトリを設定
WORKDIR /app
# アプリケーションファイルをコピー
COPY . /app
# root ユーザーのまま実行
CMD ["node", "index.js"]
❌ このままだと…
- すべてのプロセスが
root
ユーザーとして実行される - コンテナが攻撃されると、ホストシステムの制御を奪われる可能性がある
- 不要な特権が与えられている ため、攻撃対象が広がる
専用ユーザーを作成し、最小特権で実行する
FROM ubuntu:latest
# アプリケーション用のディレクトリを作成
RUN mkdir /app
# "appuser" という専用のグループとユーザーを作成(ログイン不要のシェル設定)
RUN groupadd -r appgroup && useradd -r -s /usr/sbin/nologin -g appgroup appuser
# 作業ディレクトリを設定
WORKDIR /app
# アプリケーションファイルをコピー
COPY . /app
# ユーザー権限を変更(アプリケーションディレクトリの所有者を "appuser" に設定)
RUN chown -R appuser:appgroup /app
# 最小特権ユーザーで実行
USER appuser
# アプリケーションの実行コマンド
CMD ["node", "index.js"]
✅ この設定により…
-
appuser
ユーザーで実行され、root権限なしで動作する - コンテナが侵害されても、ホストシステムに影響を与えるリスクを軽減
- 攻撃対象領域を最小化し、特権昇格を防ぐ
Node.js を alpine
イメージで運用する場合、nodeという専用ユーザーがすでにバンドルされています。
以下は一般的なノードユーザーを使用したNode.js
の例です。
FROM node:21-alpine
WORKDIR /app
COPY . /app
RUN chown -R node:node /app
USER node
CMD ["node", "index.js"]
Node.js アプリケーションを開発している場合は、公式のDockerおよびNode.jsのベストプラクティスを参照することをお勧めします。
MITM攻撃を緩和する
3. イメージ署名と検証によってDockerイメージの真正性を保証することはセキュリティ上の重要な課題です。
本番環境で実行されるコンテナの元となるイメージが、信頼できる発行者
によって提供されたものであることを確認することは不可欠です。
しかし、イメージが改ざんされるリスクはさまざまな経路で発生する可能性があります。
Dockerイメージの改ざんが発生するケース
1. MITM(Man-in-the-middle)攻撃
- Dockerクライアント ⇄ レジストリの通信が盗聴・改ざんされ、悪意のあるイメージが配布される
2. 不正なイメージのプッシュ
- 攻撃者がレジストリアカウントを乗っ取り、正規のリポジトリにマルウェアを仕込んだイメージをプッシュする
3. 信頼できないソースからのイメージ取得
-
Docker Hub
や他のレジストリで、悪意のある第三者が正規のイメージに見せかけた偽のイメージをアップロードする
このような攻撃を防ぐために、Dockerイメージの署名と検証 を適切に行うことが重要になってきます。
Dockerイメージを検証する
Dockerでは、デフォルトではイメージの真正性を検証せずにプルすることができます。
このため、発信元や作成者が不明な恣意的なDockerイメージが利用されるリスクがあります。
本番環境でのセキュリティを確保するためには、ポリシーに関係なく、イメージを取得する前にその真正性を検証することがベストプラクティスです。
Docker Content Trust(DCT)を利用した検証
DockerではDocker Content Trust(DCT) を利用することで、署名付きイメージのみをプル・プッシュ可能にすることができます。
従来はNotary v1
に基づいていましが、現在では Notary v2
への移行が進んでおり、DockerはDCTの強化を進めています。
Notary v2 とは?
従来のNotary v1
よりも軽量かつ高速に署名検証ができます。
Sigstore (Cosign)との統合が進んでおり、今後の標準的な署名方法になりそうです。
現在、DockerはNotary v2(Sigstoreベース)へ移行を進めていますが、DCTを活用したセキュアな運用は引き続き有効です。
署名付きイメージをプッシュする
以下のコマンドを使用して、一時的に DCT
を有効にし、イメージの署名とプッシュを行います。
# DCT を有効化
export DOCKER_CONTENT_TRUST=1
# 署名付きイメージのプッシュ
docker push myaccount/myimage:1
実行結果(署名の作成)
The push refers to repository [docker.io/myaccount/myimage]
64ab2aa20cf9: Layer already exists
b99937123ca0: Layer already exists
...
orig: digest: sha256:c221d4dc80b8a0d3866602020b09722d942157c720273d325a0496a529b5fcab size: 4093
Signing and pushing trust metadata
You are about to create a new root signing key passphrase. This passphrase
will be used to protect the most sensitive key in your signing system.
...
Successfully signed docker.io/myaccount/myimage:1
このプロセスでは、Dockerがイメージの署名を作成し、署名付きメタデータ
をプッシュします。
また、ルートキーやリポジトリキーのパスフレーズを設定することで、署名情報を安全に管理できます。
未署名イメージのプルを防ぐ
それでは、未署名のイメージをプルしようとした場合を考えてみましょう。
DCTを有効
にした状態で、署名されていないイメージをプルすると、リクエストは拒否され、イメージの取得がブロックされます。
export DOCKER_CONTENT_TRUST=1
docker pull untrusted/image:latest
❌ 実行結果
# 未署名イメージの拒否
Error: remote trust data does not exist for image tag 'untrusted/image:latest'
この仕組みにより、未署名イメージの誤使用リスクを軽減し、イメージが真正な発行者によってプッシュされた可能性を高めることができます。
Dockerイメージへの署名
Dockerイメージの出所と真正性を保証するためには、署名付きの信頼できるイメージを優先することが重要です。
特に、Docker Hub
によって精査・認定された公式イメージやDocker認定のイメージを利用することで、安全性が向上します。
しかし、それだけでは完全に改ざんリスクを排除できるわけではありません。
そのため、Docker Notaryを活用してイメージに署名し、改ざん検知を行うことで、さらなるセキュリティの強化が可能です。
先ほど出てきたDCT(Docker Content Trust) の仕組みは、このNotaryという機能の上で実装されているみたいですね。
Docker Notaryを使用したイメージ署名
Dockerは、Notary を使用してイメージに署名し、追加の保護レイヤーを提供します。
Notaryによる署名を有効にすると、署名が無効なイメージの実行がブロックされ、改ざん
やMITM攻撃
を防ぐことが可能になります。
では、実際に署名の手順について見ていきましょう。
Docker イメージの署名手順
Docker Content Trust(DCT)
が有効な場合、イメージをビルド・プッシュすると自動的に署名が付与されます。
# Docker Content Trust を有効化
export DOCKER_CONTENT_TRUST=1
# イメージをプッシュ(Notary による署名付き)
docker push myaccount/myimage:1
この処理により、初回の署名時にユーザーの秘密鍵が生成され、~/docker/trust
に保存されます。
この秘密鍵は、以降のイメージ署名時に使用され、信頼性を担保します。
💡 詳細な手順については、Dockerの公式ドキュメントを参照してください。
NotaryとGPGの違い
Dockerイメージの署名には Docker Notary
を活用する方法と GPG(GNU Privacy Guard)
を使用する方法がありますが、これらにはどのような違いがあるのでしょうか。
以下が簡単にまとめたものになります。
-
Docker Notary(DCT)
- ✅ イメージの改ざん防止に特化しており、署名付きイメージのみをプル・実行可能 にすることで、MITM攻撃や不正なイメージの利用を防ぐ
- ✅ レジストリレベルでの信頼管理を行い、コンテナ環境に特化したセキュリティ対策を提供
- ✅ リプレイ攻撃(過去の正規な署名データを悪用する攻撃)に対して脆弱性がある可能性 あり
-
GPG(GNU Privacy Guard)
- ✅ 個別のファイルやデータの真正性を検証するためのツールであり、リプレイ攻撃の防止に優れる
- ただし、Docker イメージの署名やプル時の検証を直接管理する機能はない
より詳細な説明は引用・参考文献の動画をご覧ください。
4. オープンソースの脆弱性を検知、修正、監視する
Dockerコンテナのベースイメージを選択する際は、単に動作するだけでなく、セキュリティのリスク にも注意を払う必要があります。
ベースイメージには、以下のようなセキュリティ上の懸念が含まれている可能性があります
- 不適切なデフォルト設定(OSのセキュリティ機能が無効化されているなど)
- 不要なシステムライブラリ(未使用のパッケージが含まれ、脆弱性を持つ可能性がある)
これらに対応するために最初にやるべきことは、問題が発生せずにアプリケーションを実行できるようにすると同時に、可能な限り最小化したベースイメージを選択(alpine
, distroless
, scratch
など)することです。
これをすることで、不要なライブラリを含まないで済むので、攻撃対象を最小化することが可能です。
しかし、これだけでは十分ではありません。
ベースイメージを最適化しても、将来的に新たな脆弱性が発見される可能性があります。
したがって、継続的な監査とセキュリティチェックが不可欠です。
継続的な監査とセキュリティチェック
定期的に イメージの脆弱性をスキャン し、既知の問題を検出・修正しましょう。
以下に、オープンソースセキュリティソフトウェアの脆弱性から保護するツールを採用し、使用中のすべての Dockerイメージレイヤーに存在するかもしれない脆弱性に対して継続的なDockerセキュリティスキャンと監視を追加することです。
🔍 脆弱性スキャンツールの例
Trivy(軽量で高速なコンテナスキャナー)
Trivyは、Aqua Securityによって開発された軽量 & 高速な脆弱性スキャナーです。
コンテナイメージだけでなく、ホスト・リポジトリ・SBOMなどもスキャン可能なのが特徴です。
またセキュリティスキャンの実行は以下のコマンドで実行できます。
trivy image myapp:latest
実行結果は、脆弱性の詳細と重要度(CRITICAL
, HIGH
, MEDIUM
, LOW
)が表示されます。
Snyk(詳細なレポートと修正提案がある)
snyk container test myapp:latest
Snyk
を使用した、Dockerイメージのスキャン例が以下の通りです。
このDockerfileに対して、snyk
コマンドを使用します。
FROM node:21-alpine
RUN mkdir /usr/src
RUN mkdir /usr/src/goof
RUN mkdir /tmp/extracted_files
COPY . /usr/src/goof
WORKDIR /usr/src/goof
RUN npm update
RUN npm install
EXPOSE 3000
CMD ["npm", "start"]
snyk container test node:21-alpine --file=Dockerfile
こちらのコマンドを実行する、以下の情報が得られます。
- ✅ スキャン対象情報
- Dockerイメージ名 (node:21-alpine)
- OSの種類 (alpine)
- アーキテクチャ (linux/amd64)
- ✅ 検出された脆弱性
- 影響を受けるパッケージ(例: openssl, libc-bin)
- CVE ID(例: CVE-2022-23219)
- 深刻度(High / Medium / Low)
- 修正可能なバージョン
- ✅ 推奨される修正方法
- 修正バージョンのアップグレード案内
- ✅ スキャン結果の概要
- 検出された脆弱性の数
- スキャンされたパッケージ数
- Snyk の詳細レポート取得案内(--json オプションあり)
Docker公式の脆弱性スキャン
こちらを使用する場合、Docker Hubにログインする必要があります。
🔨 Docker Scoutのセットアップ
- Docker Hubにログイン
docker login
→ 成功するれば、認証情報がローカルに保存されます。
2. イメージのタグ付け
ビルドしたイメージを、Docker Hub のリポジトリ用のタグに変更します。
docker tag demo-container <your-dockerhub-username>/demo-container
- ✅ docker tag <ローカルのイメージ名> <Docker Hub のリポジトリ名>
- Docker Hubへpush
docker push demo-container <your-dockerhub-username>/demo-container
こちらのコマンドで、Docker Hubにdemo-container
イメージがpushされます!
Docker Hubのリポジトリページで確認することができます。
Sigstore(Cosign でコンテナ署名と検証)
Sigstoreは、オープンソースのソフトウェアサプライチェーンのセキュリティ強化を目的としたプロジェクトで、その中の Cosign
を使うことで、コンテナイメージにデジタル署名を付与し、後から検証できるようになります。
🔨 Cosignのセットアップ
- Cosignのインストール
Cosignのインストールは、以下のページを参照してください。
https://docs.sigstore.dev/cosign/system_config/installation/
- コンテナイメージの署名
以下の手順で署名が行えます。
✅ 署名用の鍵ペアを生成
cosign generate-key-pair
こちらのコマンドで、次の2つのファイルが生成されます。
-
cosign.key
(秘密鍵) -
cosign.pub
(公開鍵)
これらの鍵はイメージ署名の際に使用します。
✅ イメージに署名
cosign sign --key cosign.key <your-dockerhub-username>/sample-app
✅ 署名鍵の検証
cosign verify --key cosign.pub <your-dockerhub-username>/sample-app
成功すると、署名情報が表示されます。
キーレス署名について
Cosign
は、従来の「鍵ペアを使用した署名」に加えて、キーレス署名 をサポートしています。
これにより、秘密鍵を管理する必要なく、GitHub Actions
やCI/CD
などのワークフローと統合しやすくなっています。
✅ キーレス署名のメリット
- 秘密鍵の管理不要 → 署名にハードウェアキー(HSM)やKMSを必要とせず、安全性が向上
-
OIDC(OpenID Connect)
対応 → GitHubやGoogle Cloudでのワークフローと連携が容易 -
CI/CD
パイプラインとの統合がシンプル → 署名&検証を自動化できる
キーレス署名の主な流れは以下のとおりです
- OIDC トークンの取得
- 開発者は、Google や GitHub などの OpenID Connect (OIDC) プロバイダーを通じて認証を行い、OIDC トークンを取得します
- 一時的な鍵ペアの生成
- Cosignは、取得した OIDCトークンを使用して短期間(約10分間有効)の鍵ペアを生成します
- 署名の実行
- 生成された一時的な鍵ペアを使用して、コンテナイメージやアーティファクトに署名を行います
- 透明性ログへの記録
- 署名情報は、Rekorなどの透明性ログに記録され、公開されます。これにより、署名の検証や監査が可能となります
このプロセスにより、秘密鍵の長期的な管理が不要となり、セキュリティリスクが低減されます。
Chainguard(Chainguard Imagesでセキュアなベースイメージを利用)
Chainguard Imagesとは
🐙通常のコンテナイメージ(例えば alpine
, debian
, ubuntu
)には、多くの不要なパッケージが含まれており、これが セキュリティリスクにつながることがあります。Chainguard は、最小限のパッケージで構成されたセキュアなベースイメージ を提供し、
不要な要素を排除することで コンテナの安全性とサイズの最適化 を両立しています。
つまり、セキュリティ強化とコンテナサイズの縮小に特化したコンテナプラットフォームです。
🔹 Chainguard Imagesの特徴
✅ 不要なソフトウェアを排除したミニマルなデザイン
- 脆弱性を最小限に抑えた軽量なベースイメージを提供し、不要なパッケージによる攻撃リスクを削減
✅ 自動化ビルドにより、イメージの最新性と利用可能なセキュリティパッチを確保
- 継続的な更新とパッチ適用により、最新のセキュアな環境を維持
✅ イメージ内のすべてのアーティファクトの出所を証明する高品質のビルドタイム SBOM(ソフトウェア部品表)
- 透明性のあるソフトウェアサプライチェーン管理を実現し、どのコンポーネントが含まれているか明確化
✅ Sigstore(Cosign)と連携し、イメージの署名・検証が可能
- イメージの信頼性を保証し、改ざんや不正アクセスを防止
✅ Cosign
と apko
による再現可能なビルド
- 署名付きの再現可能なイメージ を作成し、一貫性のあるセキュアな環境を確保
詳しくは公式ドキュメントを参考にしてください。
GoのChainguardイメージの環境構築
FROM cgr.dev/chainguard/go:latest-dev AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY main.go ./
RUN go build -o myapp .
# 実行ステージ(最小限のベースイメージ)
FROM cgr.dev/chainguard/static:latest
WORKDIR /app
# ビルド済みバイナリのみコピー(余計なファイルを含めない)
COPY /app/myapp .
USER testuser:testuser
CMD ["/app/myapp"]
💡 distroless
のChainguardイメージについて
通常のGo
イメージと違い、Chainguard の go:latest は distroless
なので、シェル (sh
) やパッケージマネージャー (apk
, apt
など) が含まれていません。
そのため、FROMでは cgr.dev/chainguard/go:latest-dev
を指定します。
以下のコマンドでChainguard Images正しく動作するかどうか確認できます。
docker pull cgr.dev/chainguard/go:latest
docker run --rm -it cgr.dev/chainguard/go:latest sh
公式にnginx
, python
, node
などの一般的に使われるイメージからミドルウェア用のイメージ、CLI用のイメージについての詳細が載っています。
5. Dockerイメージに秘密情報を漏洩しない
Dockerイメージをビルドする際、SSH秘密鍵
や APIトークン
などのシークレット情報をイメージに含めてしまうと、たとえ削除したとしても、Dockerのレイヤーにキャッシュされ、情報が漏洩するリスクがあります。
セキュリティを確保するために、以下の方法で Dockerイメージに機密情報を含めないようにしましょう。
📌 Dockerfileにシークレット情報を直接書かない
例えば、プライベートリポジトリからコードを取得するためのSSHキーや、APIトークンを環境変数として埋め込むのは危険です。
❌ NGな例
# SSHキーを直接コピー
COPY id_rsa /root/.ssh/id_rsa
🚨 この方法だと、レイヤーにキャッシュされ、キーが残ってしまう
✅ 対策として Dockerfile
の外部で管理しましょう
📌 マルチステージビルドを活用
Dockerのマルチステージビルドを使用すると、機密情報を含む中間コンテナを破棄できるため、安全にシークレットを使用できます。
マルチステージビルドの例
# Stage 1: シークレットを使用するビルド環境
FROM ubuntu as intermediate
WORKDIR /app
# シークレット情報を一時的に利用(中間コンテナ)
COPY secret/key /tmp/
RUN scp -i /tmp/key build@acme/files .
# Stage 2: 本番環境用のクリーンなイメージ
FROM ubuntu
WORKDIR /app
# 中間イメージからアプリケーションのみコピー(シークレットは含まれない)
COPY /app .
📌 Docker の --mount=type=secret を活用
Dockerの--mount=type=secret
機能を使うことで、シークレット情報を安全にマウントできます。
✅一時的にマウントする例
# syntax = docker/dockerfile:1.0-experimental
FROM alpine
# デフォルトのシークレットのマウント
RUN cat /run/secrets/mysecret
# カスタムパスにマウント
RUN cat /foobar
この方法だと、シークレットはキャッシュされずに、一時的にマウントされるので安全です。
📌 再帰コピー(COPY . .)に注意
COPY . .
はビルドコンテキスト全体をDockerイメージにコピーするため、機密ファイルが誤ってイメージに含まれる可能性があります。
COPY . .
❌この方法だと.env
やprivate.key
などの機密ファイルが含まれる可能性がある!
このような機密ファイルがある場合、.dockerignore
を活用し機密情報を除外しましょう。
✅ .dockerignore
の例
private.key
.env
appsettings.json
Dockerコンテナをどのように保護するか
本番環境でのセキュリティを確保するために、以下の対策を徹底しましょう。
✅ マルチステージビルドを活用
本番環境用のDockerイメージには、開発用のアセットやシークレット情報を含めないようにする。
✅ セキュリティツールでスキャン
コンテナのセキュリティツールを使用し、本番環境にデプロイする前に脆弱性をスキャンしましょう。
🛠️ スキャンする環境
- CLIから直接スキャン
-
Docker Hub
にあるイメージをスキャン - Amazon ECR / Google GCR などのコンテナレジストリをスキャン
6. 不変性を確保するためのタグ管理
Dockerイメージには 複数のタグ(バリアント)
を持たせることができます。
その中でもlatest
タグは特に一般的ですが、不変ではなく、同じタグが異なるバージョンのイメージを指す可能性があるため、セキュリティリスクが生じます。
この問題を回避し、Docker のセキュリティと安定性を向上させるために、適切なタグ管理を行いましょう。
latest
タグを避けるべきなのか
なぜ- バージョンが不明確になる
-
latest
タグは固定されていないため、いつの間にか新しいバージョンのイメージに更新されてしまう可能性がある - ある日突然、意図しない変更が加えられたバージョンを使うことになり、アプリが動かなくなることも
- 環境によって異なるバージョンが実行される
- ローカル環境では
古いlatest
のイメージを使っているのに、本番環境では新しいlatest
のイメージがプルされるといった問題が発生する - これにより、環境ごとに動作が異なるという一貫性のない状態になってしまう
- 新たな脆弱性が含まれる可能性がある
- latestタグのイメージはアップデートのたびに内容が変わるため、ある日突然、新しい脆弱性が含まれたイメージを使用してしまう可能性がある
- 特定の脆弱性を回避したい場合でも、どのバージョンを使っているのか明確でないと対応が難しくなる
では、どうすればいいのか見ていきましょう。
適切なタグ管理で安全な Docker運用を
📌 具体的なタグを使用する
❌ NG例
# 毎回異なるイメージが取得される可能性あり
FROM node:latest
より特定のタグ(例: :8.0.1 や :8.0.1-alpine)を使用し、環境の一貫性を確保しましょう。
✅ 固定バージョンの使用
# 安定したバージョンを指定
FROM node:18.16.0-alpine
📌 SHA-256 ハッシュを活用
特定のタグを使うよりも、SHA-256
参照を使用すれば完全に同じイメージを取得できるため、セキュリティと一貫性がより強固になります。
✅ SHA-256 ハッシュを使用
# 特定のバージョンの完全なハッシュを指定
FROM node@sha256:3b6e9c1a85bd5c4a8d4cb1f79dc9c0191b5d6cd10ecf0a7c89df36f7a0e512a4
📌 イメージのローカルミラーを作成
特定のイメージタグが削除されたり更新されたりすると、それに依存するチームのシステムが動作しなくなるリスク があります。
この問題を回避するために、自分が管理するレジストリ(例: AWS ECR
, Google GCR
, 自社のプライベートレジストリ
)にイメージのローカルミラーを作成することを検討しましょう。
ただし、このアプローチにはレジストリの管理・メンテナンスが必要 になるため、運用コストを考慮することが重要です。
ADD
のリスクを回避し、安全なCOPY
を選択
7. Docker では、ホストマシンからファイルをコピーしてDocker イメージをビルドする際に、COPY
とADD
の2つのコマンドが使用できます。
一見似ていますが、機能の違いがセキュリティに影響を及ぼす可能性があるため、適切に使い分けることが重要です。
ADDとCOPYの違い
コマンド | 機能 |
---|---|
COPY |
ローカルファイルを再帰的にコピー(シンプルで安全) |
ADD |
COPY の機能に加え、アーカイブの自動展開 & リモートURLのダウンロードをサポート |
もう少し詳しく見ていきましょう。
COPY
を使うべきなのか
なぜ ✅ COPY を使うべき理由
- シンプルで明示的 → 意図しない動作を防げる
- 不要な機能を持たない → 余計なリスクを回避
- アーカイブの自動展開をしない → Zip爆弾攻撃のリスクを減らせる
- リモートファイルのダウンロードをしない → MITM(中間者攻撃)を防ぐ
❌ ADD を使うと起こりうるリスク
- リモートURLを直接指定できるため、悪意のあるコンテンツをダウンロードするリスク
- Zipファイルを自動展開するため、Zip爆弾や解凍時の脆弱性が発生する可能性
- 意図しないファイル操作が発生する可能性がある
COPY
と ADD
の違いを理解したところで、実際に どのような場面で COPY
を使うべきか を見ていきましょう。
ADD
が許容されるケース
📌 基本的にはCOPY
を推奨しますが、例外として以下の場合のみADD
を使用できます。
✅ ローカルアーカイブ(.tar, .gz など)を自動的に展開したい場合
# ローカルの tar.gz ファイルを自動解凍
ADD archive.tar.gz /app/
ただし、手動で解凍できるなら COPY
+ RUN tar
を推奨!
COPY archive.tar.gz /tmp/archive.tar.gz
RUN tar -xzf /tmp/archive.tar.gz -C /app/ && rm /tmp/archive.tar.gz
📌 COPYを使うべきケース
✅ 安全なローカルファイルコピー
COPY app/ /app/
❌ ADD を使う場合(非推奨)
# `ADD` だと余計なリスクが発生
ADD app/ /app/
ADD
でのリモートダウンロードの危険性
📌 ADD
を使うと、リモートのURLから直接ファイルをダウンロードできてしまうため、MITM攻撃のリスクが高まります。
❌ 危険なADD
の使い方
ADD https://example.com/file.tar.gz /app/
この方法だと、リモートのファイルが改ざんされるリスクがあります。
✅ RUN wget または RUN curl を使う
リモートファイルをダウンロードする場合は、COPY
ではなくwget
やcurl
を使い、ハッシュ検証を行いましょう。
RUN wget -q https://example.com/file.tar.gz -O /tmp/file.tar.gz && \
echo "3b6e9c1a85bd5c4a8d4cb1f79dc9c0191b5d6cd10ecf0a7c89df36f7a0e512a4 /tmp/file.tar.gz" | sha256sum -c - && \
tar -xzf /tmp/file.tar.gz -C /app/ && \
rm /tmp/file.tar.gz
こうすることで、ダウンロードしたファイルが改ざんされていないか検証でます。
8. 適切なメタデータラベルでDockerイメージを整理
Dockerイメージラベルは、イメージに関するメタデータを付与する仕組みです。
適切なラベルを追加することで、イメージの管理が容易になり、ユーザーが使用方法を理解しやすくなります。
なぜメタデータラベルが重要なのか
Docker イメージに メタデータラベルを適切に設定することで、管理の透明性を向上 させ、トラブルを防ぐことができます。
具体的には、以下のメリットがあります。
✅ イメージの作成・管理者情報を明示できる
✅ ビルドやリリース情報を記録し、追跡可能にする
✅ 品質やセキュリティの状態を示せる
✅ 外部ツール(CI/CD, セキュリティ監査)との連携がしやすくなる
📌 基本的なラベルの追加方法
最も一般的なラベルは maintainer
(イメージの管理者情報) です。
以下のように、LABEL
コマンドを使用してメタデータを追加できます。
このラベルを追加することで、誰がイメージを管理しているかを明示できます。
LABEL maintainer="me@acme.com"
推奨される追加メタデータ
Dockerイメージには、メンテナンス情報だけでなく、ソースコードの出どころやバージョン管理に関するメタデータを追加することが推奨されます。
以下のような情報を含めることで、管理の透明性が向上し、長期的な運用がスムーズになります。
ラベル名 | 目的 |
---|---|
org.opencontainers.image.source |
ソースコードのリポジトリ(例: GitHub, GitLab) |
org.opencontainers.image.revision |
コミットハッシュ(どのバージョンかを明示) |
org.opencontainers.image.vendor |
開発元の企業・組織名 |
org.opencontainers.image.licenses |
使用しているライセンス情報 |
org.opencontainers.image.version |
イメージのバージョン情報 |
org.opencontainers.image.description |
イメージの説明 |
securitytxt |
セキュリティポリシーの参照(SECURITY.TXT の URL) |
また以下のように、複数ラベルを追加することで管理しやすいDockerイメージが作成できます。
LABEL maintainer="me@acme.com" \
org.opencontainers.image.title="MyApp" \
org.opencontainers.image.description="This is a sample application." \
org.opencontainers.image.version="1.2.3" \
org.opencontainers.image.source="https://github.com/acme/myapp" \
org.opencontainers.image.revision="abc123def456" \
org.opencontainers.image.licenses="MIT" \
securitytxt="https://www.example.com/.well-known/security.txt"
security.txt
の活用
📌 セキュリティを考慮し、Dockerラベルには責任ある開示ポリシーを示すSECURITY.TXT
(RFC 5785)ファイルを含めることがベストプラクティスです。
これはセキュリティ脆弱性の報告先を明示し、透明性を確保するためのものです。
Dockerイメージのラベルの詳細については、Label Schema をご覧ください。
9. マルチステージビルドで小規模かつ安全な Docker イメージを作成する
Dockerfileを使用してアプリケーションをビルドすると、開発ツール・一時ファイル・テスト用のパッケージなど、本番環境には不要なアーティファクトも含まれてしまいます。
これらの不要なデータを残すと、以下の問題が発生します。
不要なアーティファクトがあると起こる問題
❌ Dockerイメージのサイズが肥大化 → ダウンロードやデプロイに時間がかかる
❌ 不要なパッケージが含まれる → 攻撃対象領域(セキュリティリスク)が増える
❌ 本番環境には不要な開発ツールが残る → 実行環境が不必要に複雑化
📌 マルチステージビルドを活用
この問題を解決するためにマルチステージビルドを活用します。
マルチステージビルドを使用すると、ビルドプロセスで作成された一時イメージを削除し、最終的に本番環境に必要なデータだけを含めた小規模なイメージを作成できます。
📌 マルチステージビルドの流れ
マルチステージビルドでは、以下の2つのイメージを作成します。
1. ビルド用のイメージ
- 開発ツールやテスト環境を含む(コンパイル用の環境)
2. 本番用の最小イメージ - 不要なファイルを削除し、最小構成にする(動作に必要なアーティファクトのみ)
Golangのマルチステージビルド実装例
GolangはGoコンパイラ
を使って実行可能なバイナリを作成できるため、本番環境ではGoコンパイラを含める必要がありません。
そのため、マルチステージビルドを使用すると、最小構成の軽量なイメージを作成できます。
以下のDockerfileの例では、Golangのマルチステージビルドを活用して、最小限の本番用イメージを作成しています。
# ステージ 1: ビルド環境
FROM golang:1.19 AS build
WORKDIR /app
# 依存関係をダウンロード
COPY go.mod go.sum ./
RUN go mod download
# アプリケーションのコードをコピーしてビルド
COPY . .
RUN go build -o myapp
# ステージ 2: 本番用の軽量イメージ
FROM alpine:latest
WORKDIR /app
# ビルドしたバイナリのみをコピー
COPY /app/myapp .
# 実行コマンド
CMD ["./myapp"]
10. linterの活用
Dockerfileを適切に管理するために、linter(静的解析ツール)を導入しましょう。
linterを使用することで、よくあるミスを自動的に検出し、ベストプラクティスに従った記述ができるようになります。
これは、Dockerfileのセキュリティ問題を静的に分析するDockerセキュリティスキャンの重要な要素でもあります。
linterのメリット
linterには、以下のメリットが挙げられます。
✅ ミスを自動検出し、品質を向上
✅ ベストプラクティスに従ったコードを維持
✅ エンジニア間でのコードの統一性を保つ
✅ セキュリティリスクを未然に防ぐ
こうしたlinterの一つとしてhadolintがあります。(他にも、dockle, Amazon ECR Image scanningがあります)
hadolintを使うことで、Dockerfileを解析し、ベストプラクティスに沿わない記述を警告・修正提案してくれます。
hadolintは以下の方法で導入可能です。
# macOS(Homebrew)
brew install hadolint
# Linux(バイナリをダウンロード)
wget -O /usr/local/bin/hadolint https://github.com/hadolint/hadolint/releases/latest/download/hadolint-Linux-x86_64
chmod +x /usr/local/bin/hadolint
また、VScodeの拡張機能としても利用できます。リアルタイムでDockerfileのエラーを検出できるので便利です。
hadolint
を使ったDockerfileのチェック結果
🔎 以下のコマンドを実行することで、Dockerfileの静的解析が行えます。
docker run --rm -i hadolint/hadolint < Dockerfile-test
# 出力結果
/dev/stdin:3 DL4000 MAINTAINER is deprecated
/dev/stdin:6 DL3008 Pin versions in apt get install. Instead of `apt-get install <package>` use `apt-get install <package>=<version>`
/dev/stdin:6 DL3009 Delete the apt-get lists after installing something
/dev/stdin:6 DL3016 Pin versions in npm. Instead of `npm install <package>` use `npm install <package>@<version>`
/dev/stdin:6 DL3015 Avoid additional packages by specifying `--no-install-recommends`
/dev/stdin:17 DL3020 Use COPY instead of ADD for files and folders
/dev/stdin:18 DL3020 Use COPY instead of ADD for files and folders
エラーコードと対応する修正方法が出力されます。
おわりに
ここまで、Snykの記事を基に、Docker のセキュリティベストプラクティスを整理しました。
本記事で整理した 10 項目を意識することで、セキュアで効率的なDocker運用 を実現できます。
また、hadolint
などの静的解析ツールを活用してチューニングし、Snyk
や Trivy
で定期的に脆弱性をチェックすることでよりベストプラクティスに則った、安全なイメージを構築することができます。
セキュリティは、一度対策すれば終わりではなく、継続的な監視と改善が求められます。
最新の脆弱性情報やDockerのベストプラクティスを学ぶことで、より安心して使えるコンテナ環境を整えることができます!
引用・参考文献
Discussion