Skip to content
Dockerワークショップパート8: イメージビルドのベストプラクティス

イメージビルドのベストプラクティス

イメージのレイヤー化

docker image history コマンドを使うと、各レイヤーがどのように作成されたかを確認できます。

  1. docker image history コマンドを使用して、作成した getting-started イメージ内のレイヤーを確認します。

    $ docker image history getting-started

    以下のような出力が得られるはずです。

    IMAGE               CREATED             CREATED BY                                      SIZE                COMMENT
    a78a40cbf866        18 seconds ago      /bin/sh -c #(nop)  CMD ["node" "src/index.j…    0B                  
    f1d1808565d6        19 seconds ago      /bin/sh -c yarn install --production            85.4MB              
    a2c054d14948        36 seconds ago      /bin/sh -c #(nop) COPY dir:5dc710ad87c789593…   198kB               
    9577ae713121        37 seconds ago      /bin/sh -c #(nop) WORKDIR /app                  0B                  
    b95baba1cfdb        13 days ago         /bin/sh -c #(nop)  CMD ["node"]                 0B                  
    <missing>           13 days ago         /bin/sh -c #(nop)  ENTRYPOINT ["docker-entry…   0B                  
    <missing>           13 days ago         /bin/sh -c #(nop) COPY file:238737301d473041…   116B                
    <missing>           13 days ago         /bin/sh -c apk add --no-cache --virtual .bui…   5.35MB              
    <missing>           13 days ago         /bin/sh -c #(nop)  ENV YARN_VERSION=1.21.1      0B                  
    <missing>           13 days ago         /bin/sh -c addgroup -g 1000 node     && addu…   74.3MB              
    <missing>           13 days ago         /bin/sh -c #(nop)  ENV NODE_VERSION=12.14.1     0B                  
    <missing>           13 days ago         /bin/sh -c #(nop)  CMD ["/bin/sh"]              0B                  
    <missing>           13 days ago         /bin/sh -c #(nop) ADD file:e69d441d729412d24…   5.59MB   

    各行がイメージ内のレイヤーを表しています。表示は、ベースイメージが下にあり、最新のレイヤーが上にあります。これを使えば、各レイヤーのサイズを簡単に確認でき、大きなイメージの診断に役立ちます。

  2. いくつかの行が省略されていることに気づくでしょう。--no-trunc フラグを追加すると、完全な出力が得られます。

    $ docker image history --no-trunc getting-started

レイヤーキャッシング

レイヤーがどのように機能するかを確認したところで、コンテナイメージのビルド時間を短縮するための重要な教訓があります。レイヤーが1つでも変更されると、すべての下流のレイヤーも再作成される必要があります。

次に、getting-startedアプリ用に作成したDockerfileを見てみましょう。

# syntax=docker/dockerfile:1
FROM node:18-alpine
WORKDIR /app
COPY . .
RUN yarn install --production
CMD ["node", "src/index.js"]

イメージ履歴の出力に戻ると、Dockerfileの各コマンドがイメージ内の新しいレイヤーになることがわかります。イメージに変更を加えたとき、yarn依存関係が再インストールされなければならなかったことを思い出すかもしれません。同じ依存関係を毎回ビルドするのは効率的ではありません。

これを解決するには、Dockerfileの構造を変更して依存関係のキャッシュをサポートするようにする必要があります。Nodeベースのアプリケーションの場合、依存関係は package.json ファイルで定義されています。最初にそのファイルだけをコピーして依存関係をインストールし、その後にすべてのファイルをコピーします。これにより、package.json に変更があった場合にのみ、yarnの依存関係が再作成されるようになります。

  1. まず、package.json を最初にコピーし、依存関係をインストールしてから、他のすべてをコピーするようにDockerfileを更新します。

    # syntax=docker/dockerfile:1
    FROM node:18-alpine
    WORKDIR /app
    COPY package.json yarn.lock ./
    RUN yarn install --production
    COPY . .
    CMD ["node", "src/index.js"]
  2. docker build コマンドを使って新しいイメージをビルドします。

    $ docker build -t getting-started .

    以下のような出力が表示されるはずです。

    [+] Building 16.1s (10/10) FINISHED
    => [internal] load build definition from Dockerfile
    => => transferring dockerfile: 175B
    => [internal] load .dockerignore
    => => transferring context: 2B
    => [internal] load metadata for docker.io/library/node:18-alpine
    => [internal] load build context
    => => transferring context: 53.37MB
    => [1/5] FROM docker.io/library/node:18-alpine
    => CACHED [2/5] WORKDIR /app
    => [3/5] COPY package.json yarn.lock ./
    => [4/5] RUN yarn install --production
    => [5/5] COPY . .
    => exporting to image
    => => exporting layers
    => => writing image     sha256:d6f819013566c54c50124ed94d5e66c452325327217f4f04399b45f94e37d25
    => => naming to docker.io/library/getting-started
  3. 次に、src/static/index.html ファイルを変更します。例えば、<title> を「The Awesome Todo App」に変更してみてください。

  4. 再度 docker build -t getting-started . を使ってDockerイメージをビルドします。今回は、出力が少し異なるはずです。

    [+] Building 1.2s (10/10) FINISHED
    => [internal] load build definition from Dockerfile
    => => transferring dockerfile: 37B
    => [internal] load .dockerignore
    => => transferring context: 2B
    => [internal] load metadata for docker.io/library/node:18-alpine
    => [internal] load build context
    => => transferring context: 450.43kB
    => [1/5] FROM docker.io/library/node:18-alpine
    => CACHED [2/5] WORKDIR /app
    => CACHED [3/5] COPY package.json yarn.lock ./
    => CACHED [4/5] RUN yarn install --production
    => [5/5] COPY . .
    => exporting to image
    => => exporting layers
    => => writing image     sha256:91790c87bcb096a83c2bd4eb512bc8b134c757cda0bdee4038187f98148e2eda
    => => naming to docker.io/library/getting-started

    まず、ビルドがはるかに速くなっていることに気づくはずです。また、いくつかのステップが以前にキャッシュされたレイヤーを使用していることもわかります。これにより、イメージのプッシュやプル、およびそれらの更新がより迅速になります。

マルチステージビルド

マルチステージビルドは、複数のステージを使用してイメージを作成するための非常に強力なツールです。以下のような利点があります。

  • ビルド時の依存関係と実行時の依存関係を分離する
  • 実行に必要なものだけを含めてイメージのサイズを削減する

Maven/Tomcatの例

Javaベースのアプリケーションをビルドする場合、ソースコードをJavaバイトコードにコンパイルするためにJDKが必要です。しかし、JDKは本番環境では必要ありません。また、MavenやGradleなどのビルドツールも使用するかもしれませんが、それも最終的なイメージには必要ありません。ここでマルチステージビルドが役立ちます。

# syntax=docker/dockerfile:1
FROM maven AS build
WORKDIR /app
COPY . .
RUN mvn package
 
FROM tomcat
COPY --from=build /app/target/file.war /usr/local/tomcat/webapps 

この例では、最初のステージ(build と呼ばれる)でMavenを使用してJavaビルドを行います。2番目のステージ(FROM tomcat から始まります)では、build ステージからファイルをコピーします。最終イメージは最後のステージのみで作成され、--target フラグを使って上書きすることができます。

Reactの例

Reactアプリケーションをビルドする場合、Node環境を使用してJSコード(通常はJSX)、SASSスタイルシートなどを静的HTML、JS、CSSにコンパイルする必要があります。サーバーサイドレンダリングを行わない場合、本番環境にはNode環境は不要です。静的リソースをnginxコンテナに配信することができます。

# syntax=docker/dockerfile:1
FROM node:18 AS build
WORKDIR /app
COPY package* yarn.lock ./
RUN yarn install
COPY public ./public
COPY src ./src
RUN yarn run build
 
FROM nginx:alpine
COPY --from=build /app/build /usr/share/nginx/html

このDockerfileの例では、node:18 イメージを使ってビルドを実行し(レイヤーキャッシングを最大限に活用)、その後、出力をnginxコンテナにコピーしています。

まとめ

このセクションでは、イメージビルドに関するベストプラクティス(レイヤーキャッシングとマルチステージビルド)について学びました。

関連情報:

次のステップ

次のセクションでは、コンテナに関する学習を続けるために役立つ追加リソースについて学びます。