はじめに
コンテナイメージのビルド時に、APIキーやクレデンシャルなどの何らかのシークレット情報が一時的に必要になるケースがあります。やり方を間違えるとシークレットが漏洩する場合があります。本稿ではイメージビルド時にホストからビルドコンテナへ一時シークレットを安全に渡す方法を説明します。
結論: シークレットマウントを使う
シークレットマウントを使うと、シークレット情報をイメージレイヤーやビルド履歴に残さずにホストからビルドコンテナへ安全に渡すことができます。
以下、具体的な使用方法です。題材例として、ビルド処理中にAWSへアクセスするために認証情報をホストからビルドコンテナに渡すことを取り上げます (Dockerfileは説明を目的としたものであり実用性はありません)。
クレデンシャルファイルを渡すケース
FROM amazon/aws-cli
RUN --mount=type=secret,id=aws_credentials <<EOF
export AWS_SHARED_CREDENTIALS_FILE=/run/secrets/aws_credentials
aws s3 cp s3://Bucket/foo . || :
EOF
docker image build . \
--secret id=aws_credentials,src=${HOME}/.aws/credentials
buildコマンドラインから指定されたクレデンシャルファイルを、ビルドコンテナにてシークレットマウント経由で取得し、そのパスを環境変数 AWS_SHARED_CREDENTIALS_FILE
に設定してビルド処理内部でAWSへアクセスしています。
以下ポイントです:
- Dockerfile側:
-
RUN
命令で--mount=type=secret
オプション (シークレットマウントオプション) を指定することで秘密裡にホストからデータを受け取ることができる。データの中身はイメージレイヤーやビルド履歴に残らない。 - 受け取るデータは
id
値で識別される (値は任意。例ではid=aws_credentials
)。シークレットマウントオプションは複数指定可能。 - デフォルトでシークレットデータは
/run/secrets/{id値}
のパスを通じてアクセスできる。-
RUN --mount=type=secret,id=foo,target=/path/to/secret
のようにtarget
を指定すればデフォルト以外のパスも使用できる。
-
-
- buildコマンド側:
--secret
オプションでid
にシークレットマウントと同じid
値を指定し、src
にホスト側のシークレットファイルのパスを指定する。
環境変数を渡すケース
シークレットマウントでは、ファイルではなく環境変数を渡すこともできます。以下は、ホスト側の環境変数 AWS_ACCESS_KEY_ID
, AWS_SECRET_ACCESS_KEY
をビルドコンテナへ安全に渡す例です。
FROM amazon/aws-cli
RUN --mount=type=secret,id=key_id,env=AWS_ACCESS_KEY_ID \
--mount=type=secret,id=key,env=AWS_SECRET_ACCESS_KEY \
aws s3 cp s3://Bucket/foo . || :
docker image build . \
--secret id=key_id,env=AWS_ACCESS_KEY_ID \
--secret id=key,env=AWS_SECRET_ACCESS_KEY
先述のクレデンシャルファイルの例と比べると、おおよそ意味が把握できると思いますが、以下がポイントです。
- Dockerfile側:
- シークレットマウントオプションの
env
に指定した名前の環境変数が作成され、ホストから渡された値が設定される。-
ARG
命令と異なり、ビルド履歴に残らない。 -
ENV
命令と異なり、コンテナイメージに永続化されない。
-
- シークレットマウントオプションの
- buildコマンド側:
-
--secret
オプションのenv
に渡したい環境変数名を指定する。変数値ではなく変数名である点に注意 (これによりコマンドライン履歴に値を残してしまう心配がない)。- 省略記法として、
id
値に環境変数名をそのまま指定すればenv
を省略できる (ただしDockerfile側は省略できない)。
- 省略記法として、
-
ファイルを環境変数として / 環境変数をファイルとして渡すこともできる (おまけ)
使用頻度は比較的少ないかもしれないが、ホストのファイルをコンテナの環境変数として渡したり、反対にホストの環境変数をコンテナのファイルとして渡すこともできます。
以下は使い方の例です。
ファイルを環境変数として渡す
FROM alpine
RUN --mount=type=secret,id=file_to_env,env=FILE_TO_ENV \
echo "\$FILE_TO_ENV=$FILE_TO_ENV" && stop
$ echo 'This is file from host' > file
$ docker image build --secret id=file_to_env,src=file .
(...略...)
> [stage-0 2/2] RUN --mount=type=secret,id=file_to_env,env=FILE_TO_ENV echo "$FILE_TO_ENV=****" && stop:
0.635 $FILE_TO_ENV=This is file from host
環境変数をファイルとして渡す
FROM alpine
RUN --mount=type=secret,id=ENV_TO_FILE \
cat /run/secrets/ENV_TO_FILE; echo && stop
$ export ENV_TO_FILE='This is environment variable from host'
$ docker image build --secret id=ENV_TO_FILE .
(...略...)
> [stage-0 2/2] RUN --mount=type=secret,id=ENV_TO_FILE cat /run/secrets/ENV_TO_FILE; echo && stop:
0.414 This is environment variable from host
シークレットの危険な渡し方
以下で取り上げる一時シークレットの渡し方は、意図に反してシークレットがイメージに残ってしまう危険な例です。情報漏洩リスクが極めて大きいので絶対にやってはいけません。
COPY-removeパターン (危険)
シークレットファイルをCOPY
命令でコンテナにコピーし、使い終わったあとに RUN rm
で削除する方法です。最終結果だけをみれば一見ファイルが削除できているように見えるので問題ないと誤解する方がいるかもしれませんが、コンテナイメージにはCOPY
したシークレットファイルがしっかり残っています。
FROM amazon/aws-cli
COPY .aws /root/.aws
RUN <<EOF
aws s3 cp s3://Bucket/foo . || :
rm -rf /root/.aws
EOF
docker image build ~ -f Dockerfile -t unsecret:copy-remove
シークレット窃取方法
Dockerfile内に記述した各命令の結果は、イメージの構成要素として、イメージ内部にレイヤー状に積み重ねられて保存されています。特にCOPY
などのファイル操作に関わる命令は、命令毎の処理結果が一つ一つtar形式でイメージ内部に残っており、それぞれ独立してイメージから抜き出すことができます。コンテナイメージは、ベースイメージのレイヤーを基底として、Dockerfileの各命令がレイヤーとして記録されているというわけです。
クレデンシャルファイルをコピーするレイヤー (COPY .aws /root/.aws
) と削除するレイヤー (RUN ... rm -rf /root/.aws
)はそれぞれ分離しており、コピーレイヤーが残っている以上は、そこからクレデンシャルファイルを抜き出して窃取できます。
では、実際に抜き出してみましょう。まずは、先ほどビルドしたイメージを docker image save
コマンドでローカルに展開します。
本稿では、Docker 28.4.0を使用しています。Docker 24以前の場合は展開結果が異なります。
$ docker image save unsecret:copy-remove | tar -x
$ tree
.
├── blobs
│ └── sha256
│ ├── 0727f841555e830a24054117b5d53ecc18438e2e82fc78dd3cc766ca6bb76cab
│ ├── 17ff68c4b9bea5f06b5856240c22968ea7874490485afe314bef7dfff288ddc0
│ ├── 359b48c19a7eda2dbe5f6a063f18327c83cb0791d93b228cdbc3dd3edfb73f65
│ ├── 448e1142ca40c043b07601eba30193c0c41f8870d5f8cbb85394c9aa937adad6
│ ├── 522e56c3db243ee4750373e5ed12559e6714340badabbc2d3bcc1de496b14825
│ ├── 67e026f94fdfc3c3b828b1ea304b409d3cbc7e064e82045fbc7600a38a7415f2
│ ├── 959de936080be6b138609d7a6a6ab382a16f0d36bbc73d5bc4fb10ad6318c8cd
│ ├── 9cad100601e54fcc5f37048337c64efcf9355dec98efaf8b5ea940a1aedcc41e
│ ├── ad4664a08041d4ac373f3ba80abb77c2afd98c47b9a2c9f25c53823a96c7e414
│ ├── b0f9eb6ae07dd2c61cb1613944d1e03bcf82c3d650daa435acd4f5229f48c24f
│ ├── dbe4eaa7fa780a581e2a9991697e94627111feb2246816e68602649faed4391b
│ ├── e5abad3e713a2c2bc69d660d95dae2404edc35470b23fc6f299f6262d1b17c32
│ └── ffa64c02a279b9594dfff2802cb29e5557796339797026b3fb198b03559637d9
├── index.json
├── manifest.json
└── oci-layout
$ tar -tzf blobs/sha256/ad4664a0*
root/
root/.aws/
root/.aws/config
root/.aws/credentials
$ tar -xOzf blobs/sha256/ad4664a0* root/.aws/credentials
[default]
aws_access_key_id = AKIAIOSFODNN7EXAMPLE
aws_secret_access_key = wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
クレデンシャルを削除したはずのイメージから、クレデンシャル窃取に成功しました。
イメージ内部構造に関する補足
イメージのレイヤーファイルは、blobs/sha256/
配下に保存されています。レイヤー以外のファイルもあります。ファイル名はsha256ハッシュ値なので名前からはどれがレイヤーか判別できません。Dockerでビルドしたイメージを展開するとトップに manifest.json
というファイルが得られ、ここに情報が載っています。
$ jq . manifest.json
[
{
"Config": "blobs/sha256/b0f9eb6ae07dd2c61cb1613944d1e03bcf82c3d650daa435acd4f5229f48c24f",
"RepoTags": [
"unsecret:copy-remove"
],
"Layers": [
"blobs/sha256/0727f841555e830a24054117b5d53ecc18438e2e82fc78dd3cc766ca6bb76cab",
"blobs/sha256/448e1142ca40c043b07601eba30193c0c41f8870d5f8cbb85394c9aa937adad6",
"blobs/sha256/67e026f94fdfc3c3b828b1ea304b409d3cbc7e064e82045fbc7600a38a7415f2",
"blobs/sha256/e5abad3e713a2c2bc69d660d95dae2404edc35470b23fc6f299f6262d1b17c32",
"blobs/sha256/959de936080be6b138609d7a6a6ab382a16f0d36bbc73d5bc4fb10ad6318c8cd",
"blobs/sha256/ad4664a08041d4ac373f3ba80abb77c2afd98c47b9a2c9f25c53823a96c7e414",
"blobs/sha256/ffa64c02a279b9594dfff2802cb29e5557796339797026b3fb198b03559637d9"
]
}
]
Layers
配列が、各レイヤーファイルのパスの一覧です。ベースレイヤーから順に記載されています。中身はgzip圧縮のtarballです。それぞれ展開して、中身を取り出すことが可能です。
ARGパターン (危険)
buildコマンドの --build-arg
を使って渡した引数は、ARG
命令を通じてビルドコンテナ内では一時的な環境変数として使用できます。ARG
は、ENV
と異なりイメージに残らないと認識している方がいるかもしれませんが、ARG
値はコンテナイメージ内部にビルド履歴情報としてしっかり記録されるので、イメージにアクセスできればアクターは誰でもARG
値を窃取できます。
実際に、ARG
を用いたシークレット情報のやりとりは、Dockerfile Reference にも危険であると明記されています。
FROM amazon/aws-cli
ARG AWS_ACCESS_KEY_ID
ARG AWS_SECRET_ACCESS_KEY
RUN aws s3 cp s3://Bucket/foo . || :
$ env | grep 'AWS_.*KEY'
AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE
AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
$ docker image build -t unsecret:arg . \
--build-arg AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID} \
--build-arg AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY}
シークレット窃取方法
ビルド履歴は、docker image history
コマンドで簡単に確認できます。見てみましょう。
$ docker image history --format '{{.CreatedBy}}' --no-trunc unsecret:arg
RUN |2 AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY /bin/sh -c aws s3 cp s3://Bucket/foo . || : # buildkit
ARG AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
ARG AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE
/bin/sh -c #(nop) ENTRYPOINT ["/usr/local/bin/aws"]
/bin/sh -c #(nop) WORKDIR /aws
/bin/sh -c #(nop) COPY dir:ac700318f39dde8c8b7f4798e4a165435175eaf9334e07b42b201e97471c26fb in /usr/local/bin/
/bin/sh -c #(nop) COPY dir:1a110cdca7ad9228f567c289816cffad531e6fab3823e6ce54bb2b6d6f2994b0 in /usr/local/aws-cli/
/bin/sh -c dnf update -y && dnf install -y less groff findutils jq && dnf clean all
CMD ["/bin/bash"]
ADD al2023-container-raw-2023.8.20250908.0-amd64.tar.xz / # buildkit
ARG
で渡したクレデンシャルがばっちり取得できました。ビルド履歴には、ベースイメージも含めて最終イメージ作成までに実行されたDockerファイルの命令が記録されています。新しいものから順に表示されます。ARG AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE
行よりも下のエントリはベースイメージ由来のものです。
なお、ビルド履歴の情報は、イメージ内部のイメージコンフィグファイルに記録されてます。イメージを展開して直接確認することも可能です。イメージコンフィグはJSON形式になっており、その history
配列にビルド履歴の各エントリが記載されています。イメージコンフィグの構造については、OCIイメージフォーマット仕様のOCI Image Configurationを参照してください。
コンフィグファイルのパスは、Dockerの場合 manifest.json
の Config
キーから取得できます。
以下が生のビルド履歴を読み出すコマンドです。
$ docker image save unsecret:arg | tar -x
$ jq -r '.[0].Config' manifest.json | xargs jq '.history'
[
{
"created": "2025-09-06T00:39:38.427489338Z",
"created_by": "ADD al2023-container-raw-2023.8.20250908.0-amd64.tar.xz / # buildkit",
"comment": "buildkit.dockerfile.v0"
},
{
"created": "2025-09-06T00:39:38.427489338Z",
"created_by": "CMD [\"/bin/bash\"]",
"comment": "buildkit.dockerfile.v0",
"empty_layer": true
},
{
"created": "2025-09-12T18:16:36.062344603Z",
"created_by": "/bin/sh -c dnf update -y && dnf install -y less groff findutils jq && dnf clean all"
},
{
"created": "2025-09-12T18:16:41.181526869Z",
"created_by": "/bin/sh -c #(nop) COPY dir:1a110cdca7ad9228f567c289816cffad531e6fab3823e6ce54bb2b6d6f2994b0 in /usr/local/aws-cli/ "
},
{
"created": "2025-09-12T18:16:42.451523886Z",
"created_by": "/bin/sh -c #(nop) COPY dir:ac700318f39dde8c8b7f4798e4a165435175eaf9334e07b42b201e97471c26fb in /usr/local/bin/ "
},
{
"created": "2025-09-12T18:16:42.639014418Z",
"created_by": "/bin/sh -c #(nop) WORKDIR /aws"
},
{
"created": "2025-09-12T18:16:42.76272951Z",
"created_by": "/bin/sh -c #(nop) ENTRYPOINT [\"/usr/local/bin/aws\"]",
"empty_layer": true
},
{
"created": "2025-09-18T09:54:49.762964635Z",
"created_by": "ARG AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE",
"comment": "buildkit.dockerfile.v0",
"empty_layer": true
},
{
"created": "2025-09-18T09:54:49.762964635Z",
"created_by": "ARG AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
"comment": "buildkit.dockerfile.v0",
"empty_layer": true
},
{
"created": "2025-09-18T09:54:49.762964635Z",
"created_by": "RUN |2 AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY /bin/sh -c aws s3 cp s3://Bucket/foo . || : # buildkit",
"comment": "buildkit.dockerfile.v0"
}
]
さいごに
ビルド時の一時シークレットは、シークレットマウントを使用して渡してください。自分で思いついた独自のやり方は、セキュアではないかもしれないと考えた方が無難でしょう。