BuildKitによりDockerとDocker Composeで外部キャッシュを使った効率的なビルドをする方法

<font color="gray">※ 本記事は [WESEEK Tips wiki](https://tips.weseek.co.jp/) の記事 "/Tips/BuildKitによりDockerとDocker Composeで外部キャッシュを使った効率的なビルドをする方法" の転載です。</font>

# 本記事を執筆したモチベーション

Docker Hub の Automated Build 機能を GitHub Actions へ移行する機会がありました。

移行をするだけであれば GitHub Actions で docker コンテナをビルドすればよいのですが、せっかくなので BuildKit を調べて使ってみることにしました。

本記事では BuildKit についての概要を記載し、Docker Hub にキャッシュを保存して効率よくビルドする方法について記載します。
また、Docker においてのみでなく Docker Compose においても BuildKit を使ってビルドする方法を紹介します。

※ BuildKit を使った Docker ビルドの目玉機能の一つであるセキュリティ向上に関する機能は実践していません。(時間があれば追記します)

# BuildKit とは

BuildKit とはビルドツールキットです。
ソースコードを変換して**効率的**で**表現力豊か**で**再現可能な方法**で成果物を構築することが出来ます。

以下の特徴があります。[(参考)](https://github.com/moby/buildkit)

* 自動ガベージコレクション
* フロントエンドフォーマットの拡張
* 並列的な依存関係の解決
* 効率的な命令キャッシュ
* ビルドキャッシュのインポート/エクスポート
* ネストされたビルドジョブの呼び出し
* 分散ワーカー
* 複数のアウトプットフォーマット
* プラガブルなアーキテクチャ
* 管理者権限が不要な実行

BuildKit は [moby](https://github.com/moby) プロジェクトが作成したビルドツールキットであり、Docker とは独立しているプロジェクトで開発されたものです。

Docker や Docker Compose で正式に利用できるようになりつつあるため、しばしば Docker の新しいビルド機能として紹介されています。


# BuildKit の概要

BuildKit は `buildkitd` デーモンと `buildctl` クライアントにより構成されています。
`buildkitd` はデフォルトで gRPC による通信を受け付けます。

BuildKit のビルドは LLB(Low-Level intermediate build format)※ と呼ばれる中間形式のバイナリを基にして行われます。
LLB は DAG (有向非巡回グラフ)構造を持つため、ビルドステップの依存関係グラフを定義するために使用されます。

<font color="gray">※ LLB の名称は docker con 18 の発表資料に書かれた[資料](https://www.slideshare.net/Docker/containerd-buildkit-breakout) の内容を採用し、moby project ブログ [Introducing BuildKit](https://blog.mobyproject.org/introducing-buildkit-17e056cc5317) にある内容は採用しませんでした </font>

Frontend と呼ばれる BuildKit に含まれるコンポーネントが Dockerfile 等のハイレベルの言語で記述されたファイルを LLB へ変換します。
Frontend には `gateway.v0` や `dockerfile.v0`(開発中のみの存在) が存在し、dockerfile frontend を使うとホスト内のファイルが参照でき、gateway frontend を使うとネットワーク越しのファイルを参照することが出来ます。

ビルドした結果は次に示すとおり、いくつかの形式で出力でき、オプションによりレジストリへプッシュしたり、圧縮・展開などを行うことが出来ます。

* ファイル
* OCI tarball
* Docker tarball (`docker load` 出来る形式)
* containerd image store

ビルドキャッシュは出力先と形式をいくつか指定でき、出力先と形式の違いにより定義された次に示す `exporter` が用意されています。

* inline exporter
    * キャッシュをイメージへ埋め込み、レジストリへ共にプッシュする
    * (キャッシュをインポートする際は `--import-cache type=registry,ref=...` を指定する必要があるため注意)
* registry exporter
    * イメージとキャッシュを分離してプッシュする
* local exporter
    * ローカルディレクトリへ出力する

# Docker における BuildKit の導入

## 導入メリット

BuildKit を使って Docker コンテナイメージをビルドするメリットは具体的に次のとおりです。

* 複数ステージのビルドを並列実行できる
* 機密情報をビルド成果物(キャッシュを含む)に残さない仕組み(Dockerfile 内の `RUN --mount` コマンド)が使える([参考1](https://docs.docker.com/develop/develop-images/build_enhancements/), [参考2](https://github.com/moby/buildkit/blob/master/frontend/dockerfile/docs/experimental.md))
    * ローカルファイルをビルド時のみに使える
    * リモートファイルをビルド時にSSH接続して取得できる
* ビルドキャッシュのインポート/エクスポートが出来る
* 分散ビルドが出来る (将来的に出来るようになること)

## Docker バージョンに対する BuildKit 使用方法

Docker 18.06 にて BuildKit が試験的に導入され、Docker 18.09 以降で正式に導入されました。
このバージョンでは環境変数を設定して一時的に BuildKit を有効にする方法と、設定ファイルによりデフォルトで有効にする方法があります。

* 環境変数を設定する方法
    * `DOCKER_BUILDKIT=1` を設定する
    * 例えば `DOCKER_BUILDKIT=1 docker build .` により一時的に BuildKit を使ってビルド出来ます
* 設定ファイルに設定する方法
    * `/etc/docker/daemon.json` に `features.buildkit: true` を設定する
    * 例えば以下の値を設定して dockerd を再起動します
        * ※ `docker build --help` を実行して `--secret` オプションが表示されれば BuildKit が使えるようになっていることが分かります

```yml:/etc/docker/daemon.json
{
    "features": {
        "buildkit": true
    }
}
```

また、Docker 19.03 ではさらに機能が強化された上で `docker/buildx` プラグインとして実装されました。([Docker Buildx](https://docs.docker.com/buildx/working-with-buildx/))
このバージョンでは試験機能 (experimental features) モードを有効にすることで `docker buildx <COMMAND>` により BuildKit を使うことが出来ます。
例えば `docker buildx build .` により BuildKit を使ったビルドを行うことが出来ます。

試験機能モードを有効にするためには config.json (デフォルトでは `~/.docker/config.json` に保存される) に `"experimental": "enabled"` を指定します。
このとき、環境変数 `DOCKER_BUILDKIT=1` を設定する必要はありません。また dockerd の再起動は不要です。

```json:~/.docker/config.json
{
    "experimental": "enabled"
}
```

```bash
$ docker -v
Docker version 19.03.6, build 369ce74a3c
$ docker buildx --help

Usage:  docker buildx COMMAND

Build with BuildKit

Management Commands:
  imagetools  Commands to work on images in registry

Commands:
  bake        Build from a file
  build       Start a build
  create      Create a new builder instance
  inspect     Inspect current builder instance
  ls          List builder instances
  rm          Remove a builder instance
  stop        Stop builder instance
  use         Set the current builder instance
  version     Show buildx version information

Run 'docker buildx COMMAND --help' for more information on a command.
```

### ビルドコマンド

buildx プラグインを使わない場合は通常の `docker build` コマンドによりビルド出来ます。

```bash:BuildKitを利用したイメージビルド(環境変数を使う場合)
$ DOCKER_BUILDKIT=1 docker build .
```

buildx プラグインを使う場合は `docker buildx build` によりビルド出来ます。

```bash:BuildKitを利用したイメージビルド(docker/buildxプラグインを使う場合※Docker19.03以降)
$ docker buildx build .
```

### ビルドキャッシュの利用

デフォルトでビルドキャッシュは **BuildKit内部** に保存されます。
ここで、ビルドキャッシュが保存される場所は `docker` ドライバを使う場合は dockerd が動作するホスト内であり、 `docker-container` ドライバを使う場合は BuildKit が動作するコンテナ内です。

外部のビルドキャッシュに保存する場合は `--cache-to` で指定します。(オプションは `docker-container` ドライバの場合のみ指定可能)

外部のビルドキャッシュを使用する場合は `--cache-from` オプションにより指定します。

#### Docker Hub にキャッシュを保存して効率よくビルドする方法

Docker Hub にキャッシュを保存して効率よくビルドする方法は次のとおりです。

```bash:外部キャッシュを使ってビルドし、イメージをキャッシュと共にプッシュする
$ DOCKERHUB_REPOSITORY=＜YOUR_NAME＞/＜REPOSITORY_NAME＞
$ docker buildx build \
--tag ${DOCKERHUB_REPOSITORY} \
--platform linux/amd64 \
--cache-from type=registry,ref=${DOCKERHUB_REPOSITORY} \
--cache-to type=inline \
--push \
.
```

#### Docker Hub のキャッシュを使ってビルドし、ローカルに保存する方法

Docker Hub のビルドキャッシュを使ってビルドし、ローカルに保存する方法は次のとおりです。
ビルドしたイメージにバージョン等のタグをつける場合に有効です。

```bash:外部キャッシュを使ってビルドし、イメージをローカルに保存する
$ DOCKERHUB_REPOSITORY=＜YOUR_NAME＞/＜REPOSITORY_NAME＞
$ docker buildx build \
--tag ${DOCKERHUB_REPOSITORY} \
--platform linux/amd64 \
--cache-from type=registry,ref=${DOCKERHUB_REPOSITORY} \
--load \
.

$ VERSION=＜VERSION(ex. "0.3.0")＞
$ docker image tag ${DOCKERHUB_REPOSITORY} ${DOCKERHUB_REPOSITORY}:${VERSION}
```

BuildKit のビルドキャッシュと Docker のビルドキャッシュは別の場所に保存されますが、docker history で同様に確認することが出来ます。

#### ビルドキャッシュの確認

`docker history` コマンドにイメージ名を指定すると出力される結果の `IMAGE` 列を見るとイメージをビルドした時に作成されたキャッシュが確認できます。(`docker image ls -a` コマンドによりキャッシュされたイメージが確認できます)
これらのキャッシュはイメージが削除されると破棄されます。

```bash:BuildKit未使用時のビルドキャッシュ
$ docker history 2cae1c7f3bb1
IMAGE               CREATED              CREATED BY                                      SIZE                COMMENT
2cae1c7f3bb1        30 seconds ago       /bin/sh -c #(nop)  CMD ["backup" "prune" "li…   0B
8a8ba5ba5ed8        30 seconds ago       /bin/sh -c #(nop)  ENTRYPOINT ["/opt/bin/ent…   0B
96a1b883328d        30 seconds ago       /bin/sh -c #(nop) WORKDIR /opt/bin              0B
aa7a6ac5bc35        31 seconds ago       /bin/sh -c #(nop) COPY dir:ebefed72581170cd5…   10.6kB
c14edf2fc444        31 seconds ago       /bin/sh -c #(nop)  ENV AWS_DEFAULT_REGION=ap…   0B
64972185d8d4        31 seconds ago       |2 CLOUD_SDK_URL=https://dl.google.com/dl/cl…   309B
b77564bb5b09        34 seconds ago       |2 CLOUD_SDK_URL=https://dl.google.com/dl/cl…   5.13MB
f9a28314aea4        41 seconds ago       |2 CLOUD_SDK_URL=https://dl.google.com/dl/cl…   251MB
6cceb62a25f0        About a minute ago   /bin/sh -c #(nop)  ARG CLOUD_SDK_URL=https:/…   0B
dbda18957072        About a minute ago   /bin/sh -c #(nop)  ARG CLOUD_SDK_VERSION=281…   0B
76f445c07602        About a minute ago   /bin/sh -c pip install awscli                   68.6MB
fb8673a8e244        About a minute ago   /bin/sh -c apk add --no-cache     coreutils …   165MB
0fe716eb3f28        About a minute ago   /bin/sh -c #(nop)  LABEL maintainer=WESEEK <…   0B
6d1ef012b567        12 months ago        /bin/sh -c #(nop)  CMD ["/bin/sh"]              0B
<missing>           12 months ago        /bin/sh -c #(nop) ADD file:aa17928040e31624c…   4.21MB
```

```bash:BuildKit使用時のビルドキャッシュ
$ docker history 7f2334605161
IMAGE               CREATED              CREATED BY                                      SIZE                COMMENT
7f2334605161        40 seconds ago       CMD ["backup" "prune" "list"]                   0B                  buildkit.dockerfile.v0
<missing>           40 seconds ago       ENTRYPOINT ["/opt/bin/entrypoint.sh"]           0B                  buildkit.dockerfile.v0
<missing>           40 seconds ago       WORKDIR /opt/bin                                0B                  buildkit.dockerfile.v0
<missing>           40 seconds ago       COPY bin /opt/bin # buildkit                    10.6kB              buildkit.dockerfile.v0
<missing>           40 seconds ago       ENV AWS_DEFAULT_REGION=ap-northeast-1           0B                  buildkit.dockerfile.v0
<missing>           40 seconds ago       RUN |2 CLOUD_SDK_VERSION=281.0.0 CLOUD_SDK_U…   309B                buildkit.dockerfile.v0
<missing>           42 seconds ago       RUN |2 CLOUD_SDK_VERSION=281.0.0 CLOUD_SDK_U…   5.13MB              buildkit.dockerfile.v0
<missing>           47 seconds ago       RUN |2 CLOUD_SDK_VERSION=281.0.0 CLOUD_SDK_U…   251MB               buildkit.dockerfile.v0
<missing>           57 seconds ago       ARG CLOUD_SDK_URL=https://dl.google.com/dl/c…   0B                  buildkit.dockerfile.v0
<missing>           57 seconds ago       ARG CLOUD_SDK_VERSION=281.0.0                   0B                  buildkit.dockerfile.v0
<missing>           57 seconds ago       RUN /bin/sh -c pip install awscli # buildkit    68.6MB              buildkit.dockerfile.v0
<missing>           About a minute ago   RUN /bin/sh -c apk add --no-cache     coreut…   165MB               buildkit.dockerfile.v0
<missing>           About a minute ago   LABEL maintainer=WESEEK <info@weseek.co.jp>     0B                  buildkit.dockerfile.v0
<missing>           12 months ago        /bin/sh -c #(nop)  CMD ["/bin/sh"]              0B
<missing>           12 months ago        /bin/sh -c #(nop) ADD file:aa17928040e31624c…   4.21MB
```

#### ビルドキャッシュの削除

`docker` ドライバを使う場合は `docker builder prune` コマンドにより削除できます。

```bash
$ docker builder prune
WARNING! This will remove all dangling build cache. Are you sure you want to continue? [y/N] y
Deleted build cache objects:
lvle82pkre0lf8kf0mfk7oql6
e28zwrr5mc29ypc8rx9kiexre
kyjw2zvkv5nnm0r58b3jdrxsq

Total reclaimed space: 142B
```

`docker-container` ドライバを使う場合は BuildKit コンテナのシェルにて `buildctl prune` コマンドを実行することで削除できます。(`docker builder prune` で消せればよいと思うのですが消えません...)

```
$ docker ps -a
CONTAINER ID        IMAGE                           COMMAND             CREATED             STATUS              PORTS               NAMES
fec0ae39de39        moby/buildkit:buildx-stable-1   "buildkitd"         2 days ago          Up 2 days                               buildx_buildkit_elated_dewdney0
$
$ docker exec buildx_buildkit_elated_dewdney0 buildctl prune
ID                                                                      RECLAIMABLE     SIZE    LAST ACCESSED
j9uce3ql87wwe5s4mjghj2kct*                                              true            8.22kB
yvmcrshb44p02r2wggdpv7z5f                                               true            8.48kB
cwnkq42al3idkfeas0g0miv3z*                                              true            4.21kB
yr2wyc3rw11ehbp3yox7ffgx4*                                              true            4.10kB
sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10 true            8.77MB
Total:  8.79MB
```

### 非 BuildKit におけるキャッシュイン・キャッシュアウトの仕組み

`docker build` では同一 ID のイメージがある場合にキャッシュ機能が有効になります。
そのため、マルチステージビルドでキャッシュを使う場合は各ステージのイメージを push する必要があります。

docker build の `--cache-from` オプションを使ってキャッシュ機能を有効にする場合は次のようになります。

```bash:単一ステージビルドでキャッシュを利用する
$ DOCKERHUB_REPOSITORY=＜YOUR_NAME＞/＜REPOSITORY_NAME＞
$ docker pull ${DOCKERHUB_REPOSITORY}
$ docker build \
--tag ${DOCKERHUB_REPOSITORY} \
--cache-from ${DOCKERHUB_REPOSITORY} \
.
```

```bash:マルチステージビルドでキャッシュを利用する
$ DOCKERHUB_REPOSITORY=＜YOUR_NAME＞/＜REPOSITORY_NAME＞
$ docker pull ${DOCKERHUB_REPOSITORY}
$ docker pull ${DOCKERHUB_REPOSITORY}:stage1
  : 各ステージのビルド結果を pull する
$ docker build \
--tag ${DOCKERHUB_REPOSITORY} \
--cache-from ${DOCKERHUB_REPOSITORY} \
--cache-from ${DOCKERHUB_REPOSITORY}:stage1 \
  : 各ステージのビルド結果を指定する
.
```

Dockerfile はステージの数によらず上から下に 1 ステップずつビルドされます。
これは `docker build` コマンドの実行結果の `Step X/Y` を見ることで確認できます。

キャッシュは各ステップごとに作成されます。
また、キャッシュには ID が採番され、ID が同じであればキャッシュが利用されます。
ID が異なればキャッシュは使われず、同一ステージにおける以降の全てのステップではキャッシュが利用されません。

ID は `docker build` コマンドの実行結果内の ` ---> dd025cdfe837` 等と表示されます。
もしキャッシュが利用されたときは ` ---> Using cache` と表示されます。

キャッシュ ID はステップのコマンド内容によって次のように作成され、ID が変わる条件は次のとおりです。
ID が変わるとキャッシュアウトされます。

* FROM コマンドではベースとなるイメージの内容がキャッシュ ID に反映される
    * つまり、イメージが変わるとキャッシュ ID が変わる
* ADD, COPY コマンドでは COPY する元のファイル内容がキャッシュ ID に反映される
    * つまり、ファイル内容が変わるとキャッシュ ID が変わる
* LABEL, ENV, ARG コマンドでは設定する値がキャッシュ ID に反映される
    * つまり、設定する値が変わるとキャッシュ ID が変わる
* RUN, CMD, ENTRYPOINT, EXPOSE, USER コマンドではコマンド内容がキャッシュ ID に反映される
    * つまり、実行するコマンドが変わるとキャッシュ ID が変わる
    * そのため、コマンドを実行する度に結果が変わる(冪等ではない)場合はキャッシュされることを注意する必要があります


※ もし `COPY . .` を使う場合 Dockerfile が含まれないように .dockerignore を用意することをお勧めします。Dockerfile が COPY 元に含まれるとファイル変更によりキャッシュアウトするためです。

※ キャッシュを pull すると Docker Hub の pull 数が 1 より大きい値になっているようです。ただ、キャッシュが空の時に pull しても数が不定であり、仕組みがよく分かりません。('2020/03/15現在)


### BuildKit におけるキャッシュイン・キャッシュアウトの仕組み

`docker buildx build` ではイメージを pull しなくても `--cache-from` を指定することでキャッシュが利用できます。
また、マルチステージビルドの場合も結果のイメージに各ステージのキャッシュを埋め込むことが出来ます。

* CMD, ENTRYPOINT, EXPOSE, ENV, LABEL, USER が変わってもキャッシュに影響しない
* ARG は値が変わっただけではキャッシュに影響しない
    * 但し、ARG を利用したコマンドの内容が変わることで、それらのコマンドの条件に応じてキャッシュが利用されるかどうかが分かれます

これは `docker buildx build` コマンド実行結果の `[builder]` や `[stage-X X/Y]` に上記コマンドが表示されないことから確認できます。


### ビルダインスタンスを操作する (`docker/buildx`プラグインで利用可能)

ビルダインスタンスとは独立したビルド環境のことです。

`docker/buildx` プラグインではビルダインスタンスを操作することができます。

#### ビルダインスタンスを確認する

```bash:ビルドインスタンスを確認する
$ docker buildx ls
NAME/NODE DRIVER/ENDPOINT STATUS  PLATFORMS
default * docker
  default default         running linux/amd64, linux/386
```

#### ビルダインスタンスを作成する

利用中の docker 設定がdefault の値として適用されます。

default は `docker` ドライバが使われますが、次のようにいくつか機能が制限されています。

* `--output` オプションで使用できるのは local, tarball exporter, image exporter のみである
* `--cache-from` オプションで使用できるのは registry type のみである
* `--cache-to` オプションで使用できるのは inline type のみである

`docker-container` ドライバを使うにはビルダインスタンスを作成する必要があります。

```bash:ビルダインスタンスを作成する
$ docker buildx create --name docker-container --driver docker-container --use
docker-container
```

```bash
# ビルダインスタンスが作成され、使用するように設定されていることを確認する
$ docker buildx ls
NAME/NODE           DRIVER/ENDPOINT             STATUS   PLATFORMS
docker-container *  docker-container
  docker-container0 unix:///var/run/docker.sock inactive
default             docker
  default           default                     running  linux/amd64, linux/386

# docker-container が起動していないことを確認する (docker-container ビルダインスタンスの STATUS が inactive である)
$ docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES

# 一度ビルドを行ってみる
$ docker buildx build .
  : <snip>

# すると、コンテナが起動して STATUS が running になる
$ docker ps -a
CONTAINER ID        IMAGE                           COMMAND             CREATED              STATUS              PORTS               NAMES
3622af1006df        moby/buildkit:buildx-stable-1   "buildkitd"         About a minute ago   Up About a minute                       buildx_buildkit_docker-container0
$ docker buildx ls
NAME/NODE           DRIVER/ENDPOINT             STATUS  PLATFORMS
docker-container *  docker-container
  docker-container0 unix:///var/run/docker.sock running linux/amd64, linux/386
default             docker
  default           default                     running linux/amd64, linux/386

# ちなみに、コンテナが停止した状態だと STATUS は stopped になる
$ docker stop buildx_buildkit_docker-container0
buildx_buildkit_docker-container0
$ docker buildx ls
NAME/NODE           DRIVER/ENDPOINT             STATUS  PLATFORMS
docker-container *  docker-container
  docker-container0 unix:///var/run/docker.sock stopped
default             docker
  default           default                     running linux/amd64, linux/386
```

# Docker Compose における BuildKit の利用

BuildKit による Docker コンテナイメージビルドのメリットは具体的に次のとおりです。

* ステージビルドを並列実行できる
* 機密情報をビルド成果物(キャッシュを含む)に残さない仕組み(Dockerfile 内の `RUN --mount` コマンド)が使える([参考](https://docs.docker.com/develop/develop-images/build_enhancements/))
    * ローカルファイルをビルド時のみに使える
    * リモートファイルをビルド時にSSH接続して取得できる

Docker Compose 1.25.0 にて docker-compose build 時に BuildKit が使用できる方法が追加されました。

> Add BuildKit support, use DOCKER_BUILDKIT=1 and COMPOSE_DOCKER_CLI_BUILD=1
> [出典] https://github.com/docker/compose/releases/tag/1.25.0

環境変数を設定することで　Docker Compose において BuildKit を使ってビルドすることができます。
ただし、Docker が BuildKit を使えるように設定されていることが前提です。

また、一方で `docker buildx bake` コマンドにより docker-compose.yml を読み取ってビルドすることが出来ます。

* 環境変数を設定する方法
    * `COMPOSE_DOCKER_CLI_BUILD=1` を設定する
    * 例えば `COMPOSE_DOCKER_CLI_BUILD=1 docker-compose build` により BuildKit を使ってビルド出来ます
* `docker/buildx` プラグインを使う方法
    * buildx bake の対象ファイルとして docker-compose.yml を指定して実行します
    * 例えば `docker buildx bake -f docker-compose.yml` を実行すると BuildKit を使ってビルド出来ます

もともと Docker Compose は `docker/cli` は使わず、[docker-py](https://github.com/docker/docker-py) を使って dockerd の API にアクセスしますが、環境変数を使って BuildKit を使う仕組みは `docker/cli` を使います。

これは、執筆当時の '20/03/09 現在では docker-py は BuildKit に対応していなかった([参考](https://github.com/docker/docker-py/issues/2230)) ため、docker-compose にて BuildKit を使うために `docker/cli` を使用する方法が実装されたためです。([参考1](https://github.com/docker/compose/pull/6584), [参考2](https://github.com/docker/compose/pull/6865), [参考3](https://speakerdeck.com/orisano/docker-compose-and-buildkit))

```bash
# docker-compose が docker-py を利用していることの確認
$ docker-compose version
docker-compose version 1.25.4, build 8d51620a
docker-py version: 4.1.0
CPython version: 3.7.5
OpenSSL version: OpenSSL 1.1.0l  10 Sep 2019
```

## Docker Compose における BuildKit ビルド時に外部キャッシュを利用する

* 環境変数 `COMPOSE_DOCKER_CLI_BUILD=1` を使う場合
    * docker-compose.yml 内の `services.<service名>.build.cache_from` に外部キャッシュを指定します
    * 例えば以下の docker-compose.yml を記述して `docker-compose build` を実行するとキャッシュが有効になります
    * なお、`docker-compose up --build` でもキャッシュを利用してビルドすることが出来ます
* `docker/buildx` プラグインを使う方法
    * 事前に外部キャッシュを使ってベースイメージをビルドした後に docker-compose.yml 内のサービスをビルドします
    * 例えば以下の docker-compose.yml の場合、`docker buildx build --cache-from` を実行してベースイメージをビルドした後に `docker buildx bake -f docker-compose.yml` を実行します

環境変数を使う場合、`docker-compose build` を実行した際に、次の warning メッセージが表示されれば BuildKit によるビルドが有効になっています。

> WARNING: Native build is an experimental feature and could change at any time

```yaml:環境変数を使う場合の外部キャッシュ利用方法(docker-compose.yml)
# cache_from は v3.2 以降で利用可能です (https://docs.docker.com/compose/compose-file/)
version: '3.2'

services:
  app:
    build:
      context: .
      cache_from:
        # イメージは適当です。後続で紹介する Dockerfile を使ってビルドした結果を指定しましょう
        - ryu310/github_action_sandbox
```

```dockerfile:環境変数を使う場合の外部キャッシュ利用方法(Dockerfile)
FROM alpine:3.11.3

LABEL maintainer="Ryu Sato"

COPY scripts/entrypoint.sh /root

CMD ["/root/entrypoint.sh"]
```

```yaml:docker/buildxプラグインを使う場合の外部キャッシュ利用方法(docker-compose.yml)
version: '3'

services:
  app:
    build:
      context: .
```

```dockerfile:docker/buildxプラグインを使う場合の外部キャッシュ利用方法(Dockerfile)
FROM alpine:3.11.3

LABEL maintainer="Ryu Sato"

COPY scripts/entrypoint.sh /root

CMD ["/root/entrypoint.sh"]
```

```bash:環境変数を使う場合の外部キャッシュ利用方法
$ DOCKER_BUILDKIT=1 COMPOSE_DOCKER_CLI_BUILD=1 docker-compose build
WARNING: Native build is an experimental feature and could change at any time
  : <snip>
 => CACHED [2/2] COPY scripts/entrypoint.sh /root                                                                                                                              
  : <snip>
Successfully built d1acbb2a8544a28dd4a4d300a6caab3f0c9aabe030a96e6621cbafd9114f1f0a
```

```bash:docker/buildxプラグインを使う場合の外部キャッシュ利用方法
$ docker buildx build --cache-from type=registry,ref=ryu310/github_action_sandbox .
  : <snip>
 => CACHED [2/2] COPY scripts/entrypoint.sh /root                                                                                                                                 

$ docker buildx bake -f docker-compose.yml
  : <snip>
 => CACHED [2/2] COPY scripts/entrypoint.sh /root
```