docker buildのsecretオプションでハマった話
はじめに
こんにちは。都内でソフトウェアエンジニアをしているtomoriです。
最近 docker build
の secret
オプションでハマったことがあったので、備忘録として記事にします。
(どちらかというと secret
オプションというよりは docker build
そのものでハマっていたような気もしますが……)
ちなみに secret
オプションのことは、「要件的にこういうの欲しいなぁー」と思って探していたらたまたま見つけました。
先に結論
諸々の経緯を端折って先に結論を書きます。
Build secrets を使用してイメージをビルドする際には、以下の3点に注意してください。
-
BuildKit を使用する
- Docker Desktop, Docker Engine
v23.0
以降では、BuildKit がデフォルトのビルダーとして有効になっています。特別な設定は不要です。 - もし
v23.0
より古いエンジンを使用している場合は、以下のように環境変数を設定して BuildKit を明示的に有効化してください。$ DOCKER_BUILDKIT=1 docker build .
- Docker Desktop, Docker Engine
-
Buildx を最新バージョンにアップデートする
-
$ docker buildx version
で現在のバージョンを確認し、v0.6.0
以上であることを確認してください。 -
v0.6.0
より古い場合は、最新バージョンにアップデートしてください。
-
-
syntax
ディレクティブを使用し、最新の BuildKit フロントエンドを利用する- Dockerfile の先頭に以下の1行を追加することで BuildKit の最新フロントエンドを使用して Dockerfile を解析できます。
# syntax=docker/dockerfile:1
- Dockerfile の先頭に以下の1行を追加することで BuildKit の最新フロントエンドを使用して Dockerfile を解析できます。
secretオプションとは
Docker には Build secrets という概念があります。
Build secrets とは、コンテナイメージをビルドするプロセスの一部で使用される、パスワードやAPIキーなどの機密情報のことを指します。
A build secret is any piece of sensitive information, such as a password or API token, consumed as part of your application's build process.
従来、ビルド時の情報を引き渡す方法としては、ビルド引数(ARG
) や 環境変数(ENV
) が一般的でした。
しかし、これらの方法では、機密情報が最終的なイメージに含まれたり、ビルドキャッシュやビルドログに残ってしまうリスクがあります。
そのため、ビルドプロセスに対して機密情報を引き渡す方法としては適していません。
Build arguments and environment variables are inappropriate for passing secrets to your build, because they persist in the final image. Instead, you should use secret mounts or SSH mounts, which expose secrets to your builds securely.
この課題を解決するために登場したのが Build secrets です。
docker build
コマンドで --secret
オプションを指定することで Build secrets を使用することができ、ビルドプロセス中に限り、機密情報を一時的かつ安全に引き渡すことが可能になります。
ハマったこと
イメージのビルド環境として AWS CodeBuild を使用しており、以下のように secret
オプションを使って、ビルドプロセス内の環境変数にシークレットをマウントする設定を行っていました。
CodeBuild の環境には aws/codebuild/amazonlinux2-x86_64-standard:5.0
を指定しています。
※ 以下は簡略化した例です。
RUN \
aws s3 cp ...
...
phases:
build:
commands:
- |
docker build \
--secret id=xxx,env=XXX \
--secret id=xxx,env=XXX \
-t "${IMAGE_NAME}:${IMAGE_TAG}" \
-f "${DOCKERFILE_PATH}" \
"${BUILD_CONTEXT}"
...
ローカル環境では同様の Dockerfile とコマンドで正常にビルドできることを確認していたのですが、 CodeBuild 環境下では以下のエラーが発生しました。
Dockerfile:25
--------------------
24 |
25 | >>> RUN --mount=type=secret,id=xxx,env=XXX \
26 | >>> --mount=type=secret,id=xxx,env=XXX \
27 | >>> aws s3 cp ...
28 |
--------------------
ERROR: failed to solve: unexpected key 'env' in 'env=XXX'
試したこと1
「あれ?環境変数へのマウントってもしかすると最近追加された機能なのかな?」と思い、 docker build のリリースノートを確認したところ、 secret
オプションの env
サポートは v0.6.0
(2021年のリリース)で追加されていることが確認できました。
- Allow secrets from environment variables. docker/buildx#488
念のため buildspec.yml
に以下のコマンドを追加して Buildx のバージョンを確認しました。
$ docker buildx version
その結果、CodeBuild で使用している Buildx のバージョンは v0.14.1
であり、バージョン的には問題ないことがわかりました。
[Container] 2024/11/19 10:00:00.000000 Running command docker buildx version
github.com/docker/buildx v0.14.1 59582a88fca7858dbe1886fd1556b2a0d79e43a3
さらに、出力されたコミットハッシュ時点でのソースを確認したところ、こちらにもリリースノートに貼られていたものと同様の実装があり、やはり問題ないことが判明(52, 53行目)。
試したこと2
Buildx のバージョンは問題なさそうだったため、「BuildKit を使用できていないのでは?」と考え、次はその部分を見直すことにしました。
先ほど同様に buildspec.yml
に以下を追加し、Docker Engine のバージョンを確認しました。
$ docker --version
結果、Docker Engine のバージョンは v26.1.4
であり、デフォルトで BuildKit が使用されるはずだという結論に至りました。
(厳密には $ docker --version
はエンジンのバージョンを確認するコマンドではないのですが、大体同じ値になるのでその辺は割愛)
[Container] 2024/11/19 10:10:00.000000 Running command docker --version
Docker version 26.1.4, build 5650f9b
公式ドキュメントにも v23.0
以降の Docker Desktop, Docker Engine はデフォルトのビルダーとして BuildKit が使用されることが明記されています。
BuildKit is the default builder for users on Docker Desktop and Docker Engine v23.0 and later.
念のため、以下のように buildspec.yml
を変更し、BuildKit を明示的に有効化しました。
phases:
build:
commands:
- |
- docker build \
+ DOCKER_BUILDKIT=1 docker build \
--secret id=xxx,env=XXX \
--secret id=xxx,env=XXX \
-t "${IMAGE_NAME}:${IMAGE_TAG}" \
-f "${DOCKERFILE_PATH}" \
"${BUILD_CONTEXT}"
特に何もしなくても BuildKit は使用できているはずなので、当然これでもエラーは解消されず途方に暮れます。
解決方法
問題を解決するために調査を続けていたところ、約2ヶ月前に投稿された、同様の問題を訴える issue を発見しました。
この issue の内容を追っていくと、どうやら Dockerfile 内で Build secrets を環境変数としてマウントする env
オプションは、2024/09/10 にリリースされた BuildKit v1.10.0
で初めてサポートされたことがわかりました。
Build secrets can now be mounted as environment variables using the env=VARIABLE option. moby/buildkit#5215
さらに、この BuildKit バージョンは Docker Engine v27.3.0
以降で含まれることが明記されています。
CodeBuild 環境内の Docker Engine は v26.1.4
だったため、このバージョンには含まれていないリリースであることがわかります。
つまり、CodeBuild 環境で発生していたエラーは、 Dockerfile の解析を担当する BuildKit フロントエンドのバージョンが古かったこと が原因だったようです。
コマンドオプション解析を行う Buildx は --secret
の env
に対応していた一方で、Dockerfile の解析を行う BuildKit フロントエンドの方が --secret
の env
に対応していないバージョンだった、ということになります。
CodeBuild 環境内の Docker Engine をアップデートすることは現実的ではないため、 syntax
ディレクティブを利用して最新の BuildKit フロントエンドを明示的に指定すること で解決しました。
Dockerfile の先頭に以下の1行を追加することでsyntax
ディレクティブを利用できます。
# syntax=docker/dockerfile:1
また、特定のバージョンを使用したい場合には、以下のようにバージョンを固定することも可能です。
# syntax=docker/dockerfile:1.10.0
数時間振り回された末に、これで無事にエラーが解消されました。
あとがき
docker build
ノコトナニモワカッテナカッタンダナ。
今振り返ると、今回の問題は docker build
について以下のポイントを理解していたかどうかで、問題解決の勘所を掴めるかどうかが大きく変わるものだったなと感じています。
-
docker build
はビルドクライアントである Buildx とビルドバックエンドである BuildKit によって構成されていること。 -
docker build
コマンド実行時のオプション解析は Buildx が担当し、Dockerfile の解析は BuildKit が担当しているということ。 - BuildKit が Dockerfile を解析する際、BuildKit フロントエンドというコンポーネントを使用して解析を行っていること。
- BuildKit フロントエンドはデフォルトで BuildKit に組み込まれたものが使用されるが、
syntax
ディレクティブを利用することで外部の(最新の)ものを使用できること。
エラー直面当初は、自分もこのあたりの仕組みを全く理解しておらず、issue を見つけて解決した後に「で、結局何が原因で何をして解決したんだ?そもそも自分は何を知らないから苦労したんだ?」と考えることに。
その後、ドキュメント等をあさって色々調べた結果、ようやく自分の頭で整理して理解できました。
普段何気なく使用していただけに、Docker って奥が深いなぁと改めて実感した問題でした。
これを機に docker build
に対する解像度を上げることにも繋がったので、なんやかんやよかったと思っています。
内容的には若干マニアックだったかもしれませんが、本記事が同じような問題に直面している誰かの助けになれば嬉しいです。
Discussion