マウント

このセクションでは、Docker の構築( docker build )で キャッシュ マウント(cache mount)バインド マウント(bind mount) を使う方法を説明します。

キャッシュマウントでは、構築中に使用する固定されたパッケージのキャッシュを指定できます。 固定されたキャッシュ(persistent cache) は構築ステップの速度向上に役立ちます。特に、パッケージマネージャーを使い、パッケージのインストールが発生するステップに役立ちます。パッケージに対する固定されたキャッシュを持つのが意味するのは、レイヤを再構築したとしても、新しいまたは変更されたパッケージしかダウンロードしません。

キャッシュマウントを作成するには、Dockerfile 内の RUN 命令で --mount フラグと一緒に使います。キャッシュマウントを使うには、フラグの書式は --mount=type=cache,target=<path> であり、 <path> にはコンテナ内にマウントしたいキャッシュディレクトリの場所です。

キャッシュマウントの追加

キャッシュマウントの対象として使用するパスは、使用するパッケージマネージャに依存します。このガイドのアプリケーション例では Go モジュールを使います。つまり、キャッシュマウント対象のディレクトリとは、Go モジュールキャッシュが書き込む場所です。 Go モジュール リファレンス に従うと、モジュールキャッシュに使うデフォルトの場所は $GOPATH/pkg/mod であり、 $GOPATH 用のデフォルト値は /go です。

パッケージのダウンロードとプログラムをコンパイルする構築ステップを書き換え、キャッシュマウントとして /go/pkg/mod ディレクトリをマウントします。

  # syntax=docker/dockerfile:1
  FROM golang:{{site.example_go_version}}-alpine AS base
  WORKDIR /src
  COPY go.mod go.sum .
- RUN go mod download
+ RUN --mount=type=cache,target=/go/pkg/mod/ \
+     go mod download -x
  COPY . .

  FROM base AS build-client
- RUN go build -o /bin/client ./cmd/client
+ RUN --mount=type=cache,target=/go/pkg/mod/ \
+     go build -o /bin/client ./cmd/client

  FROM base AS build-server
- RUN go build -o /bin/server ./cmd/server
+ RUN --mount=type=cache,target=/go/pkg/mod/ \
+     go build -o /bin/server ./cmd/server

  FROM scratch AS client
  COPY --from=build-client /bin/client /bin/
  ENTRYPOINT [ "/bin/client" ]

  FROM scratch AS server
  COPY --from=build-server /bin/server /bin/
  ENTRYPOINT [ "/bin/server" ]

go mod download コマンドに -x フラグを付け、ダウンロード処理を表示します。このフラグの追加により、次のステップでキャッシュマウントがどのように使われているのか分かるでしょう。

イメージの再構築

イメージを再構築する前に、 構築キャッシュ(build cache) を片付けます。これにより、白紙状態から始められるようにし、構築で何をしているのか確実に見えるようにします。

$ docker builder prune -af

次はイメージを再構築する時です。今回は構築コマンドで --progress=plain フラグを付けて実行し、出力をログファイルにリダイレクトします。

$ docker build --target=client --progress=plain . 2> log1.txt

構築が完了すると、 log1.txt を調べます。ログからは、構築パートで Go モジュールがダウンロードされているのが分かります。

$ awk '/proxy.golang.org/' log1.txt
#11 0.168 # get https://proxy.golang.org/github.com/charmbracelet/lipgloss/@v/v0.6.0.mod
#11 0.168 # get https://proxy.golang.org/github.com/aymanbagabas/go-osc52/@v/v1.0.3.mod
#11 0.168 # get https://proxy.golang.org/github.com/atotto/clipboard/@v/v0.1.4.mod
#11 0.168 # get https://proxy.golang.org/github.com/charmbracelet/bubbletea/@v/v0.23.1.mod
#11 0.169 # get https://proxy.golang.org/github.com/charmbracelet/bubbles/@v/v0.14.0.mod
#11 0.218 # get https://proxy.golang.org/github.com/charmbracelet/bubbles/@v/v0.14.0.mod: 200 OK (0.049s)
#11 0.218 # get https://proxy.golang.org/github.com/aymanbagabas/go-osc52/@v/v1.0.3.mod: 200 OK (0.049s)
#11 0.218 # get https://proxy.golang.org/github.com/containerd/console/@v/v1.0.3.mod
#11 0.218 # get https://proxy.golang.org/github.com/go-chi/chi/v5/@v/v5.0.0.mod
#11 0.219 # get https://proxy.golang.org/github.com/charmbracelet/bubbletea/@v/v0.23.1.mod: 200 OK (0.050s)
#11 0.219 # get https://proxy.golang.org/github.com/atotto/clipboard/@v/v0.1.4.mod: 200 OK (0.051s)
#11 0.219 # get https://proxy.golang.org/github.com/charmbracelet/lipgloss/@v/v0.6.0.mod: 200 OK (0.051s)
...

次はキャッシュマウントが使われているのを確認するため、プログラムが読み込む Go モジュールのバージョンを変えます。モジュールのバージョンを変更するには、次回構築時に、Go に対して依存関係の新しいバージョンを強制的にダウンロードさせます。ですが、キャッシュマウントを追加していますので、Go は大部分のモジュールを再利用でき、 /go/pkg/mod ディレクトリ内にまだ存在していないパッケージのバージョンのみダウンロードします。

アプリケーションが使うサーバコンポーネントである chi パッケージのバージョンを更新します。

$ docker run -v $PWD:$PWD -w $PWD golang:{{site.example_go_version}}-alpine \
    go get github.com/go-chi/chi/v5@v5.0.8

次は別の構築を行い、再び構築ログをログファイルにリダイレクトします。

$ docker build --target=client --progress=plain . 2> log2.txt

次は log2.txt ファイルを調査しますと、 chi パッケージのみがダウンロードされ、更新されたのが分かるでしょう。

$ awk '/proxy.golang.org/' log2.txt
#10 0.143 # get https://proxy.golang.org/github.com/go-chi/chi/v5/@v/v5.0.8.mod
#10 0.190 # get https://proxy.golang.org/github.com/go-chi/chi/v5/@v/v5.0.8.mod: 200 OK (0.047s)
#10 0.190 # get https://proxy.golang.org/github.com/go-chi/chi/v5/@v/v5.0.8.info
#10 0.199 # get https://proxy.golang.org/github.com/go-chi/chi/v5/@v/v5.0.8.info: 200 OK (0.008s)
#10 0.201 # get https://proxy.golang.org/github.com/go-chi/chi/v5/@v/v5.0.8.zip
#10 0.209 # get https://proxy.golang.org/github.com/go-chi/chi/v5/@v/v5.0.8.zip: 200 OK (0.008s)

バインドマウントの追加

いくつかの小さな最適化により、Dockerfile を改善できます。現時点では、モジュールをダウンロードする前に、 COPY 命令を使って go.modgo.sum ファイルを取得します。これらのファイルをコンテナのファイルシステムを通してコピーするのではなく、バインドマウントが利用できます。バインドマウントはホストから直接コンテナでファイルを利用できるようにします。この変更は COPY 命令(とレイヤ)を追加する必要を完全に除去します。

  # syntax=docker/dockerfile:1
  FROM golang:{{site.example_go_version}}-alpine AS base
  WORKDIR /src
- COPY go.mod go.sum .
  RUN --mount=type=cache,target=/go/pkg/mod/ \
+     --mount=type=bind,source=go.sum,target=go.sum \
+     --mount=type=bind,source=go.mod,target=go.mod \
      go mod download -x
  COPY . .

  FROM base AS build-client
  RUN --mount=type=cache,target=/go/pkg/mod/ \
      go build -o /bin/client ./cmd/client

  FROM base AS build-server
  RUN --mount=type=cache,target=/go/pkg/mod/ \
      go build -o /bin/server ./cmd/server

  FROM scratch AS client
  COPY --from=build-client /bin/client /bin/
  ENTRYPOINT [ "/bin/client" ]

  FROM scratch AS server
  COPY --from=build-server /bin/server /bin/
  ENTRYPOINT [ "/bin/server" ]

同様に、同じテクニックを使い2つめの COPY 命令も同じく不要にできます。 build-clientbuild-server ステージ内で、現在の作業ディレクトリをバインドマウントとして指定します。

  # syntax=docker/dockerfile:1
  FROM golang:{{site.example_go_version}}-alpine AS base
  WORKDIR /src
  RUN --mount=type=cache,target=/go/pkg/mod/ \
      --mount=type=bind,source=go.sum,target=go.sum \
      --mount=type=bind,source=go.mod,target=go.mod \
      go mod download -x
- COPY . .

  FROM base AS build-client
  RUN --mount=type=cache,target=/go/pkg/mod/ \
+     --mount=type=bind,target=. \
      go build -o /bin/client ./cmd/client

  FROM base AS build-server
  RUN --mount=type=cache,target=/go/pkg/mod/ \
+     --mount=type=bind,target=. \
      go build -o /bin/server ./cmd/server

  FROM scratch AS client
  COPY --from=build-client /bin/client /bin/
  ENTRYPOINT [ "/bin/client" ]

  FROM scratch AS server
  COPY --from=build-server /bin/server /bin/
  ENTRYPOINT [ "/bin/server" ]

まとめ

このセクションでは、キャッシュとバインドマウントを使って構築速度を改善できる方法を学びました。

関連情報:

次のステップ

次セクションでは構築引数を使い、調整可能な構築をする方法をを紹介します。