よくある失敗:Dockerイメージをハッカーの「格好の餌食」にしていませんか?
自分のイメージに対して docker history コマンドを実行した際、NPM_TOKEN が丸見えになっていて驚いたことはありませんか?私はNode.jsアプリを初めてDocker化しようとした時に、このミスを犯しました。当時は、内部パッケージをダウンロードするために ARG を使ってトークンを渡し、「ビルドが終わればトークンも消えるだろう」と思い込んでいたのです。
しかし、現実はそれほど甘くありません。イメージを docker pull できる権限さえあれば、誰でも ARG の値をすべて覗き見ることができます。さらに ENV を使用している場合、そのトークンは実行中のコンテナ内にも堂々と居座り続けます。一度、Docker Hub上でAWS Access Keyを露出させてしまい、セキュリティ部門から警告を受けたことで、ようやくビルド時のシークレット管理の重要性を痛感しました。
なぜ従来のシークレット受け渡し方法は危険なのか?
現在、DevOpsの世界で避けるべきとされている3つの「伝統的」な手法を振り返ってみましょう:
1. Docker ARG の使用
これは最も古典的なミスです。--build-arg を介した値は、イメージレイヤーのメタデータに永続的に保存されます。たとえ後のレイヤーでその変数を unset したとしても、ビルド履歴の前のレイヤーにはしっかりと残っています。
2. シークレットファイルをコピーして削除する(アンチパターン)
COPY .npmrc .npmrc
RUN npm install
RUN rm .npmrc
この方法は非常に有害です。Dockerはレイヤースタッキング(層状)の仕組みで動作します。.npmrc ファイルは COPY レイヤーに存在します。RUN rm コマンドは、単にファイルが削除されたという印を付けた「新しいレイヤー」を作成するだけです。実データは依然として古いレイヤーに残っており、レイヤーを展開するいくつかのテクニックを使えば、すぐに取り出すことが可能です。
3. マルチステージビルド
この方法は、最初のステージでビルドし、後のステージでアーティファクトのみをコピーするため、比較的安全です。しかし、誤ってビルドステージごとレジストリにプッシュしてしまったり、キャッシュの保存が不適切だったりすると、漏洩のリスクは依然として残ります。
BuildKit Secrets:本番環境における新しいセキュリティ標準
Docker 18.09から、BuildKit の --secret 機能が登場し、状況は一変しました。その仕組みは非常にスマートです。シークレットはビルドプロセス中に一時的なファイル(tmpfs)としてマウントされます。これはいかなるレイヤーにも書き込まれることはありません。
複雑な設定ファイルを扱う際、シークレットを注入した後にJSONの形式を再確認したいことがよくあります。そんな時、私は toolcraft.app/ja/tools/developer/json-formatter を使って見やすく整形しています。ターミナルでわざわざ jq コマンドを叩いたり、拡張機能をインストールしたりするよりも手軽です。
Docker BuildKit Secrets の実装ガイド
ステップ1:BuildKit を有効にする
現在の Docker Desktop では、通常 BuildKit がデフォルトで有効になっています。古い Linux バージョンを使用している場合は、ビルド前に次のコマンドを実行してください:
export DOCKER_BUILDKIT=1
ステップ2:サンプルシークレットファイルの作成
例として、my_token.txt ファイル内のAPIキーを保護する必要があるとします:
echo "super-secret-api-key-2024" > my_token.txt
ステップ3:–mount=type=secret を使用して Dockerfile を構成する
ここが重要なポイントです。シークレットを使用する必要がある RUN 命令の中で、シークレットへのアクセス権を宣言する必要があります:
# syntax=docker/dockerfile:1
FROM alpine
# シークレットをデフォルトのパス /run/secrets/my_token にマウント
RUN --mount=type=secret,id=my_token \
TOKEN=$(cat /run/secrets/my_token) && \
echo "APIからデータを取得するためにトークンを使用中..." && \
curl -H "Authorization: Bearer $TOKEN" https://api.example.com/v1/setup
# RUN命令が終了すると、シークレットファイルは自動的にアンマウントされます
重要: ファイルの先頭にある # syntax=docker/dockerfile:1 という行を忘れないでください。これがないと、Dockerはこの高度なマウント構文を理解できません。
ステップ4:ビルドの実行
--secret フラグを使用して、ホストマシン上のファイルを Dockerfile 内の id にマップします:
docker build --no-cache --secret id=my_token,src=my_token.txt -t secure-app:v1 .
よくある2つの実践的なユースケース
1. プライベートな NPM/Python パッケージのインストール
設定ファイルをコピーする代わりに、root のホームディレクトリに直接マウントします:
RUN --mount=type=secret,id=npmrc,target=/root/.npmrc \
npm install
2. SSH 経由でプライベート Git リポジトリをクローンする
BuildKit は SSH を強力にサポートしています。イメージに手動でキーを渡す必要はありません:
# Dockerfile内
RUN --mount=type=ssh git clone [email protected]:org/private-core.git
ビルド時に、次のフラグを追加するだけです:docker build --ssh default .
クイック評価:メリットとデメリット
メリット:
- 絶対的な安全性: シークレットはビルド中の一時メモリ内にのみ存在します。
- クリーン: レイヤーをクリーンアップするための
rm -rfコマンドが不要です。
デメリット:
- 構文: 従来の
ARG/ENV形式に慣れている人には、少し馴染みが薄いかもしれません。 - CIシステム: 一部の古いバージョンの Jenkins や GitLab Runner では、BuildKit をサポートするために追加の設定が必要です。
1年間運用して得られた実践的な教訓
- .dockerignore の確認: シークレットファイルは必ず
.dockerignoreに含めてください。誤ってCOPY . .を実行してしまうと、BuildKit によるセキュリティ対策がすべて無意味になります。 - データの分類: 機密情報にのみ
--secretを使用してください。APP_VERSIONのような設定には、引き続きARGを使う方が便利で高速です。 - ツールによる検証: ビルド後、
diveなどのツールを使用して各レイヤーを検査してください。シークレットファイルがどこにも現れなければ、成功です。
Docker のセキュリティは、ポートの遮断や脆弱性スキャンだけではありません。それはイメージを構築する方法から始まります。この記事が、あなたのチームの CI/CD プロセスをより安全にする一助となれば幸いです。

