Docker Build Check について検証をしてみた

Keita Sato

2024.8.13

はじめに

こんにちは、Sreake 事業部 佐藤慧太@(SatohJohn) です。

以下の docker build check という機能について、検証をし、Google Cloud の Cloud Build に組み込んで見る。ということをやってみました。

https://www.docker.com/ja-jp/blog/introducing-docker-build-checks/

Docker のドキュメントには以下のようにまとまっております。

https://docs.docker.com/build/checks/

上記記事では、開発者が Dockerfile の作成、編集に対してとても苦労していることがわかっていると紹介されています。

開発者との会話の中で、多くの人がコンテナイメージを構築するためのベストプラクティスを学び、それに従うのに苦労していることがわかりました。 2024 の「アプリケーション開発の現状」レポートによると、Docker ユーザーの 35% が、Dockerfile の作成と編集を上位 3 つのタスクの 1 つとして挙げています。しかし、回答者の 55%は、Dockerfileの作成がサポートとして最も選択されているタスクであると報告しています。

https://www.docker.com/ja-jp/blog/introducing-docker-build-checks/

その意味でこのツールは Dockerfile の lint check をおこない、上記の課題に対し、既存のベストプラクティスから学べるようにしたものです。

Dockerfile の linter としては以下のようにすでにツールが存在しています。

これらと違い、docker build check は見て分かる通り docker の機能として組み込まれており、ツールを別途インストールする必要がない、というのが大きな特徴となります。

そして、 docker build の —-check オプションは build 自体をしないので、とても実行が早くおわるというのも特徴です。

利用するための Docker のバージョンは 1.8 以上、 Buildx バージョン 0.15.0 以上です。

実装

今回利用するものは、私が作ったものと言うよりは、最近アプリケーション開発に利用した https://github.com/google/mesop というフレームワークで Deploy時に利用する Dockerfile を使います。

https://google.github.io/mesop/guides/deployment/#dockerfile


FROM python:3.12.4-bullseye

RUN apt-get update && \\
  apt-get install -y \\
  # General dependencies
  locales \\
  locales-all && \\
  # Clean local repository of package files since they won't be needed anymore.
  # Make sure this line is called after all apt-get update/install commands have
  # run.
  apt-get clean && \\
  # Also delete the index files which we also don't need anymore.
  rm -rf /var/lib/apt/lists/*

ENV LC_ALL en_US.UTF-8
ENV LANG en_US.UTF-8
ENV LANGUAGE en_US.UTF-8

# Install dependencies
RUN pip install poetry \\
  && poetry config virtualenvs.create false
COPY ./pyproject.toml ./poetry.lock* ./
RUN poetry install

# Create non-root user
RUN groupadd -g 900 mesop && useradd -u 900 -s /bin/bash -g mesop mesop
USER mesop

# Add app code here
COPY . /srv/mesop-app
WORKDIR /srv/mesop-app/src

# Run Mesop through gunicorn. Should be available at localhost:8080
CMD ["gunicorn", "--bind", "0.0.0.0:8080", "main:me"]

上記の Dockerfile を build すると以下のように warning がでてきます。

docker build .
 3 warnings found (use docker --debug to expand):
 - LegacyKeyValueFormat: "ENV key=value" should be used instead of legacy "ENV key value" format (line 16)
 - LegacyKeyValueFormat: "ENV key=value" should be used instead of legacy "ENV key value" format (line 17)
 - LegacyKeyValueFormat: "ENV key=value" should be used instead of legacy "ENV key value" format (line 18)

ENV の記載方法を更新したほうが良い、との警告ですね。 build 自体は成功しています。詳細を見たい場合は —debug オプションを付けます。

docker --debug build .
 3 warnings found:
 - LegacyKeyValueFormat: "ENV key=value" should be used instead of legacy "ENV key value" format (line 16)
Legacy key/value format with whitespace separator should not be used
More info: <https://docs.docker.com/go/dockerfile/rule/legacy-key-value-format/>
Dockerfile:16
--------------------
  14 |       rm -rf /var/lib/apt/lists/*
  15 |     
  16 | >>> ENV LC_ALL en_US.UTF-8
  17 |     ENV LANG en_US.UTF-8
  18 |     ENV LANGUAGE en_US.UTF-8
--------------------

 - LegacyKeyValueFormat: "ENV key=value" should be used instead of legacy "ENV key value" format (line 17)
Legacy key/value format with whitespace separator should not be used
More info: <https://docs.docker.com/go/dockerfile/rule/legacy-key-value-format/>
Dockerfile:17
--------------------
  15 |     
  16 |     ENV LC_ALL en_US.UTF-8
  17 | >>> ENV LANG en_US.UTF-8
  18 |     ENV LANGUAGE en_US.UTF-8
  19 |     
--------------------

 - LegacyKeyValueFormat: "ENV key=value" should be used instead of legacy "ENV key value" format (line 18)
Legacy key/value format with whitespace separator should not be used
More info: <https://docs.docker.com/go/dockerfile/rule/legacy-key-value-format/>
Dockerfile:18
--------------------
  16 |     ENV LC_ALL en_US.UTF-8
  17 |     ENV LANG en_US.UTF-8
  18 | >>> ENV LANGUAGE en_US.UTF-8
  19 |     
  20 |     # Install dependencies
--------------------

この build 時に warning があった際にエラーとしたい場合はファイルの先頭に # check=error=true をつけます。

https://docs.docker.com/build/checks/#fail-build-on-check-violations

ビルドのチェック違反は、デフォルトでは終了コード 0 の警告として報告されます。Dockerfilecheck=error=trueのディレクティブを使用して、違反が報告されたときにビルドが失敗するように Docker を設定できます。これにより、ビルド チェックが実行された後、実際のビルドが実行される前に、ビルドでエラーが発生します。

https://www.docker.com/ja-jp/blog/introducing-docker-build-checks/

後に紹介をしますが、コマンドのオプションでも、可能です

docker build --build-arg "BUILDKIT_DOCKERFILE_CHECK=skip=LegacyKeyValueFormat" .

docker desktop での実行

本題になりますが、上記までの場合は build を行いチェックがされました。再度になりますが —check オプションを付けると build されないため高速にチェックができます。

docker build --check .
[+] Building 1.3s (3/3) FINISHED                                                                                                           docker:desktop-linux
 => [internal] load build definition from Dockerfile                                                                                                       0.0s
 => => transferring dockerfile: 979B                                                                                                                       0.0s
 => [internal] load metadata for docker.io/library/python:3.12.4-bullseye                                                                                  1.3s
 => [internal] load .dockerignore                                                                                                                          0.0s
 => => transferring context: 2B                                                                                                                            0.0s

WARNING: LegacyKeyValueFormat - <https://docs.docker.com/go/dockerfile/rule/legacy-key-value-format/>
"ENV key=value" should be used instead of legacy "ENV key value" format
Dockerfile:15
--------------------
  13 |       rm -rf /var/lib/apt/lists/*
  14 |     
  15 | >>> ENV LC_ALL en_US.UTF-8
  16 |     ENV LANG en_US.UTF-8
  17 |     ENV LANGUAGE en_US.UTF-8
--------------------

WARNING: LegacyKeyValueFormat - <https://docs.docker.com/go/dockerfile/rule/legacy-key-value-format/>
"ENV key=value" should be used instead of legacy "ENV key value" format
Dockerfile:16
--------------------
  14 |     
  15 |     ENV LC_ALL en_US.UTF-8
  16 | >>> ENV LANG en_US.UTF-8
  17 |     ENV LANGUAGE en_US.UTF-8
  18 |     
--------------------

WARNING: LegacyKeyValueFormat - <https://docs.docker.com/go/dockerfile/rule/legacy-key-value-format/>
"ENV key=value" should be used instead of legacy "ENV key value" format
Dockerfile:17
--------------------
  15 |     ENV LC_ALL en_US.UTF-8
  16 |     ENV LANG en_US.UTF-8
  17 | >>> ENV LANGUAGE en_US.UTF-8
  18 |     
  19 |     # Install dependencies
--------------------

このとき、Dockerfile に# check=error=true をつけていなくても、終了コードは 1 が返ってきます。

Unlike a regular build, if any violations are reported when using the --check flag, the command exits with a non-zero status code.

https://www.docker.com/ja-jp/blog/introducing-docker-build-checks/

Cloud Build での実行

私が Google Cloud が好きだから(大事)というのもありますが、Cloud Build でやるパターンも考えます。

Cloud Build の設定については省きます。ファイルはとても簡易的なものを用意しました。

steps:
  - id: docker-file-check-app
    name: "gcr.io/cloud-builders/docker"
    args: ["build", ".", "--check"]
    waitFor: ["-"]

  - id: build-app
    name: "gcr.io/kaniko-project/executor:latest"
    args:
      - --destination=${LOCATION}-docker.pkg.dev/$PROJECT_ID/app/main:latest
      - --cache=true
      - --cache-ttl=6h
    waitFor: ["docker-file-check-app"]

options:
  logging: CLOUD_LOGGING_ONLY

timeout: 60s

gcr.io/cloud-builders/docker を使って、check をして成功すれば、アプリケーションを build するというものです。サンプルとして作成しているだけですので、build 時にチェックすることでもやりたいことは達成できるとは思いますが、今回はあくまでも —-check について動かすと言うものにしています。

これを動かすと —-check option が見つからずエラーになります。

cloud logging の画面

当たり前ですが、docker のバージョンが違うためです。私が実行した際には gcr.io/cloud-builders/docker を見てみると latest は 20.10.24 と同じになっており、docker version も 20.10.24 になっております

docker artifact registory の画面

そのため、現状CIとしていれるのであれば 確かめるとしたら docker のバージョンを更新するのが良さそうです。

steps:
  - id: docker-file-check-app
    name: "gcr.io/cloud-builders/docker:24.0.9"
    args: ["build", "--check", "."]
    waitFor: ["-"]

  - id: build-app
    name: "gcr.io/kaniko-project/executor:latest"
    args:
      - --destination=${LOCATION}-docker.pkg.dev/$PROJECT_ID/app/main:latest
      - --cache=true
      - --cache-ttl=6h
    waitFor: ["docker-file-check-app"]

options:
  logging: CLOUD_LOGGING_ONLY

timeout: 60s

もう一度動かすとチェックされている事がわかります。

cloud logging の画面2
cloud logging の画面3

lint で言われた部分を修正してすると、0 が返り、build が進みます。

ENV LC_ALL=en_US.UTF-8
ENV LANG=en_US.UTF-8
ENV LANGUAGE=en_US.UTF-8
cloud build の画面

補足

対応してほしくないものについては skip をさせることができます。

例えば、先程の Dockerfile の先頭に # check=skip=LegacyKeyValueFormat と記載すると警告を skip できます。警告の種類については以下にまとまっております。

https://docs.docker.com/reference/build-checks/

また、Dockerfile に記載するのではなく、以下のように CSV 文字列を build-arg オプションに渡し実行することもできます。

docker build --check --build-arg "BUILDKIT_DOCKERFILE_CHECK=skip=LegacyKeyValueFormat" .

まとめ

warning のある Dockerfile があった場合、以下のようにまとめることができます。

error=true終了コードbuild時間
docker buildfalse0build するため長い
docker buildtrue0以外build するため長い?
(docker desktop では docker build —check と同じ動きをする)
docker build —checkfalse0以外短い
docker build —checktrue0以外短い

これを見ると分かる通り、CI のような高速に回したい機能については docker build の —check オプションは有用かと思います。

しかし、実際、利用してみたところ docker build check で確認できるものは hadolint よりも少ないという認識です。例えば、 https://hadolint.github.io/hadolint/ で触れられている Always tag the version of an image explicitly のような点については check してくれません。

また、以下記事で触れていただいているような COPY の利用についても対応されているようには見えません。

https://future-architect.github.io/articles/20240726a/

この様に、まだ発展段階と思いますが、導入自体はとても簡単ですし、まず一度試してみるのが良いかと思います。

ブログ一覧へ戻る

お気軽にお問い合わせください

SREの設計・技術支援から、
SRE運用内で使用する
ツールの導入など、
SRE全般についてご支援しています。

資料請求・お問い合わせ