Dockerfile RUN –mount は、BuildKit で使える書き方で、ビルドの1ステップの間だけ秘密情報・キャッシュ・別のファイルを見せる機能です。トークンを ARG で渡すとイメージの履歴に残りますが、RUN --mount=type=secret なら残りません。moby/buildkit(★10.4k、Apache-2.0)の Dockerfile の文書は、2022年の 1.4.3 のころは syntax.md にあり、今は reference.md に移っています。2026-11-11 時点の安定版は 1.28.0 です。この記事では、Docker 29.8.2(BuildKit v0.33.1)で実際にビルドして確かめた使い方と、1.4.3 の文書から変わった点をまとめます。
ARG vs SECRET MOUNT同じトークンを2通りで渡してイメージを調べた(2026-11-11 実測)
ARG の値はイメージの履歴に4か所残る。secret マウントは0か所
ARG で渡す
--build-arg LEAKY_TOKEN=…
- docker history に値がそのまま出る(4行)
- ARG の後の RUN の行すべてに値が付く
- docker save した設定 JSON にも残る
RUN --mount=type=secret
--secret id=api_token,src=…
- history にも層にも値は出ない
- その RUN の間だけ /run/secrets/ に見える
- 1.10 からは env= で環境変数にもできる
30秒でわかる(2026-11-11)
- ・何ができる:RUN の間だけ秘密(secret・ssh)、キャッシュ(cache)、別の段のファイル(bind)を見せる。イメージには残らない
- ・何を解決する:ARG で渡したトークンが docker history に残る問題。依存のダウンロードを毎回やり直す遅さ
- ・注意:1行目の
# syntax=を古い版に固定すると新しい書き方が通らない。cache マウントは CI の新しいマシンでは空
イメージの中身や作り方から入り込む攻撃の全体像は サプライチェーン攻撃とは|手口・防御ツール比較・npm 12の新機構まで実践解説 にまとめています。
Dockerfile の BuildKit 構文とは:RUN –mount・heredoc・COPY –link
BuildKit は Docker のビルドを担う仕組みで、Docker 23 以降は既定で使われます。Dockerfile の解釈は「フロントエンド」という別のイメージ(docker/dockerfile)が受け持ち、1行目の # syntax= でどの版を使うか選べます。1.4.3 の文書に載っていた主な書き方は、今の版でもすべて使えます。
| 書き方 | できること | 1.4.3 の文書が示す版 |
|---|---|---|
RUN --mount=type=secret |
トークン・鍵をその RUN の間だけファイルで見せる | 1.2 以上 |
RUN --mount=type=ssh |
手元の SSH エージェントを使って git clone などをする | 1.2 以上 |
RUN --mount=type=cache |
パッケージのダウンロード先などをビルドをまたいで残す | 1.2 以上 |
RUN --mount=type=bind / tmpfs |
別の段のファイルを一時的に見せる/一時的なメモリ領域 | 1.2 以上 |
RUN --network=none |
その RUN だけネットワークを切る | 例は 1.3 |
COPY --link |
前の層に依存しないコピー。土台のイメージを替えても作り直さない | 1.4 以上 |
ヒアドキュメント(<<EOT) |
複数行のスクリプトや設定ファイルをそのまま書く | 1.4.0 から |
出典: moby/buildkit の frontend/dockerfile/docs(dockerfile/1.4.3 の syntax.md、1.28.0 の reference.md)
ヒアドキュメント、COPY --link、RUN --network=none は、1.4.3 と 1.28.0 の両方で同じ結果になることを確かめました。--network=none の RUN では、外へのダウンロードが失敗します。
# syntax=docker/dockerfile:1.28.0@sha256:bb22d9815c728170f72750f4e5b0d672e06176142e1d602c7e66c050100b7e5b
FROM alpine:3.22
RUN <<EOT
set -e
echo one > /a.txt
echo two >> /a.txt
EOT
COPY --link <<EOT /etc/app.conf
port=8080
EOT
RUN --network=none sh -c 'wget -q -T 3 -O- http://example.com >/dev/null && echo NET-OK || echo NET-BLOCKED' > /net.txt
Dockerfile RUN –mount=type=secret:ARG と比べてイメージに残るか
同じ13文字のトークンを、ARG と secret マウントの2通りで渡しました。どちらの RUN も、値そのものではなく文字数だけをファイルに書き出しています。
# syntax=docker/dockerfile:1
FROM alpine:3.22
ARG LEAKY_TOKEN
RUN echo "arg-length=${#LEAKY_TOKEN}" > /arg.txt
RUN --mount=type=secret,id=api_token \
echo "secret-length=$(wc -c < /run/secrets/api_token)" > /secret.txt
RUN --mount=type=secret,id=api_token,env=API_TOKEN \
echo "env-length=${#API_TOKEN}" > /env.txt
docker buildx build --build-arg LEAKY_TOKEN=ARGSECRET-456 \
--secret id=api_token,src=token.txt -t bkt-secret:1 --load .
3つとも長さは13で、どちらの渡し方でも RUN の中では値を使えました。違いはイメージに残るものです。docker history --no-trunc では、ARG の値が ARG の行とその後の RUN 3行の計4か所にそのまま出ました。docker save で取り出したファイルを検索すると、ARG の値は設定ファイル(作成履歴)から見つかり、secret の値はどこからも見つかりませんでした。
ARG の値は、後ろのすべての RUN に「|1 LEAKY_TOKEN=…」の形で付きます。ARG を使う RUN を1つだけにしても、残る場所は減りません。secret を環境変数で受ける env= は 1.10 からの機能です。ビルド中の設定ミスで秘密がファイルに残っていないかは、Betterleaks徹底解説|Gitleaks原作者のシークレットスキャナ、v2で変わる-vと98.6%の正体 のようなスキャナでも確かめられます。
Dockerfile RUN –mount=type=cache:残る条件と消える条件
cache マウントは、パッケージのダウンロード先などをビルドをまたいで持ち越す機能です。中身の件数を数える RUN を、--build-arg で毎回キャッシュを外して3回ビルドすると、件数は 2・3・4 と増え続けました。ところが --no-cache を付けると1に戻り、空から始まりました。
| 条件 | cache マウントの中身 |
|---|---|
| 同じビルダーで続けてビルド | 残る(実測) |
--no-cache を付けたビルド |
空から始まる(実測) |
--cache-to / --cache-from で受け渡し |
含まれない(公式の文書) |
| 毎回新しいマシンで動く CI | 空から始まる(公式の文書) |
公式の文書は、cache マウントは性能のためだけに使い、中身が何であってもビルドが通るように書くよう求めています。apt のように同時に書き込めない道具には sharing=locked を付けます。BuildKit のキャッシュを本番の更新に生かす例は PHP 緊急アップデートを数分で本番に当てる|事前に仕込むDockerイメージのビルド高速化と配布基盤の作り方 で扱っています。
1.4.3 → 1.282022年の 1.4.3 の文書から変わったこと
1.4.3 の文書のままだと使えない・古いところ
-
1.--security=insecure は labs から通常版へ
1.4.3 では unknown flag。今の版でも許可(entitlement)が無いと止まる
-
2.secret を環境変数で渡す env=(1.10〜)
1.4.3 では unexpected key 'env' で失敗
-
3.COPY --parents・--exclude、RUN --device
1.4.3 には無い。--device は BuildKit 0.20 以上と device の許可が要る
-
4.cache マウントは --cache-to に入らない
毎回新しい CI ランナーでは空から始まる、と今の文書が明記
# syntax= の固定:1.4.3 のままだと何が起きるか
# syntax= は、ビルドの最初に Docker Hub などからフロントエンドのイメージを取ってきて、それに Dockerfile を解釈させる指定です。1.4.3 の文書の例をまねて # syntax=docker/dockerfile:1.4.3 に固定すると、今の書き方は次のように止まりました。
- secret の
env=:unexpected key 'env' in 'env=API_TOKEN' RUN --security=insecure:unknown flag: security(1.4.3 では labs 版だけの機能)
今の版では --security=insecure は通常版で書けますが、ビルダー側で security.insecure を許可しないと security.insecure is not allowed で止まります。--network=host と --device も同じく許可が要ります。
WHAT # syntax= DOES1行目の # syntax= で何が起きるか
1行目がビルドを解釈するイメージを選ぶ
-
# syntax= を読む
書いた人が Docker Hub などのイメージを指定する
-
フロントエンドを取得
docker/dockerfile:1 は 11/11 時点で 1.27.1 と同じイメージだった
-
Dockerfile を解釈
古い版に固定すると新しい書き方はエラー(1.4.3 で env= は不可)
-
ダイジェストで固定
tag@sha256:… と書くとダイジェストが優先される
公式は # syntax=docker/dockerfile:1 を勧めていますが、2026-11-11 時点でこのタグは 1.27.1 と同じイメージを指していました。10月7日に出た 1.28.0 とはダイジェストが違います。版を決めて動かすなら、版の番号とダイジェストを書きます。タグとダイジェストを両方書くと、ダイジェストのほうが使われます。1.28.0 のタグに 1.27.1 のダイジェストを書いた試しでは、ビルドは 1.27.1 で動きました。ダイジェストは docker buildx imagetools inspect docker/dockerfile:1.28.0 で確かめられます。
フロントエンドはビルドの中で動くプログラムなので、# syntax= に知らないイメージが書かれた Dockerfile は、中身を読んでからビルドしてください。
自分の Dockerfile とイメージを確認する
リポジトリの直下で、# syntax= の行を一覧にします。古い版に固定しているもの、ダイジェストの無いものが分かります。
grep -rn --include='Dockerfile*' -E '^# *syntax=' .
作ったイメージの履歴に、秘密らしい名前が付いた行が無いかを見ます。<image> は調べるイメージ名に置き換えます。
docker history --no-trunc <image> --format '' | grep -i -E 'token|secret|passw|api_key'
ARG でトークンを渡した検体では ARG LEAKY_TOKEN=ARGSECRET-456 を含む4行が出て、ARG を使わない検体では0行でした。このコマンドは名前で拾うだけなので、secret マウントの行(/run/secrets/ を含む)も出ます。値が書かれているのは KEY=値 の形の行です。名前に当てはまらない秘密は拾えないので、このコマンドだけでは判断できません。値が出たイメージは配布をやめ、その値(トークン・鍵)を作り直してください。履歴から値を消すことはできません。





