😄

[Docker]: Dockerのマルチステージビルドについてやってみる

に公開

[Docker]: Dockerのマルチステージビルドについてやってみる

なんとなくDockerを使用するのを卒業するという意味も込めて、ざっくりですがマルチステージビルドについて学びました。
初学者が作ったそれっぽいコンテナと思って見ていただければと思ってください。

今回はNext.js・Laravel(API)の構成を想定した以下のようなコンテナをマルチステージビルドで作ってみます。
理由としてはLaravelをDockerで構築するときにはいくつかのパッケージやライブラリ、composerをインストールするのでマルチステージビルドによるイメージサイズの違いがより数値としてより実感しやすいと考えました。
本番環境ではデータベースはAWS RDSやPostgreSQLなどデータベースサービスに, redisはUpstashやElastiCacheに接続することが予想されるので今回は割愛します。

  • app(node)
  • api(php)
  • web(nginx)
  • db(mysql)
  • cache(redis)

リポジトリ

マルチステージビルドとは

  • Dockerイメージを複数のステージに分けて構築することができる
  • FROMを使用してイメージを利用
  • ステージに分けることでそのステージごとに必要なファイルやライブラリを選択、コピーできるため不要な物を除外した軽量なイメージを作成できる
  • 不要なライブラリやツールは除外することで脆弱性を減らし、セキュリティを強化することができる。→攻撃対象を減らす

主な構文

FROM:
イメージやステージを指定する

AS:
ステージに名前をつける

COPY --from=<stage>: 指定したステージからファイルをコピーする

マルチステージビルドなしで実装

Dockerファイルのみのサイズ

test-multistage-build-app     latest    09ac8427dcf1   2 months ago    1.12GB
test-multistage-build-api     latest    582c9eb1939a   2 weeks ago     503MB
test-multistage-build-web     latest    c907445d05b9   10 days ago     192MB
test-multistage-build-db      latest    4b7ca4355544   12 days ago     776MB
test-multistage-build-cache   latest    9ad1c98c04f5   3 months ago    117MB

プロジェクトを作成した時点でのサイズ

test-multistage-build-app     latest    6e086bad9911   8 minutes ago    1.12GB
test-multistage-build-api     latest    4cfe67e869f5   5 minutes ago    623MB
test-multistage-build-web     latest    c907445d05b9   10 days ago      192MB
test-multistage-build-db      latest    4b7ca4355544   12 days ago      776MB
test-multistage-build-cache   latest    9ad1c98c04f5   3 months ago     117MB

その後playwrightなどなんやかんやインストールした最終サイズ

test-multistage-build-app     latest    1b6cf9fce580   44 seconds ago   4.3GB
test-multistage-build-api     latest    71184510e6c3   13 hours ago     623MB
test-multistage-build-web     latest    c907445d05b9   11 days ago      192MB
test-multistage-build-db      latest    4b7ca4355544   12 days ago      776MB
test-multistage-build-cache   latest    9ad1c98c04f5   3 months ago     117MB

マルチステージビルドで実装

nodeコンテナ

まず下記を参考にしつつ、コンテナを作成

###################################
#
#   開発環境
#
###################################

FROM node:22.14.0-slim AS dev

# 一時的にルートユーザーを使用
USER root

# グローバルインストール
RUN npm install -g npm@latest vercel@latest

# playwrightのインストール
RUN npx playwright install-deps
RUN npx playwright install

# 非ルートユーザーに切り替え
USER node

# ディレクトリの指定
WORKDIR /home/node/app

###################################
#
#   ビルド環境
#
###################################

FROM dev AS build

# 非ルートユーザーに切り替え
USER node

# ディレクトリの指定
WORKDIR /home/node/app

COPY --chown=node:node ./app/package*.json /home/node/app/
COPY --chown=node:node ./app /home/node/app/

RUN npm ci

# ビルドを実行
RUN npm run build

# 不必要なパッケージを削除
RUN npm prune --production

###################################
#
#   本番環境
#
###################################

FROM gcr.io/distroless/nodejs22-debian12:nonroot AS production

# ディレクトリの指定
WORKDIR /app

# ビルドステージからファイルをコピー
COPY --from=build --chown=nonroot:nonroot /home/node/package*.json ./
COPY --from=build --chown=nonroot:nonroot /home/node/app/next.config.ts ./
COPY --from=build --chown=nonroot:nonroot /home/node/app/public ./public
COPY --from=build --chown=nonroot:nonroot /home/node/app/.next ./.next
COPY --from=build --chown=nonroot:nonroot /home/node/app/node_modules ./node_modules

# 環境変数を設定
ENV NODE_ENV production

# Next.jsアプリケーションを起動
CMD ["node_modules/.bin/next", "start"]

結果
大体1/6にできました。このうちnode_modulesのサイズは482MBです。まだ余分なパッケージがあるとは思うのですが、なかなか上手くいきませんでした。

test-multistage-build-app     latest    fc6def45b2f2   8 minutes ago   675MB

phpコンテナ

###################################
#
#   開発環境
#
###################################

FROM php:8.4-fpm AS dev

WORKDIR /home/php/api

RUN apt-get update \
    && apt-get install -y \
    build-essential \
    git \
    zip \
    unzip \
    vim \
    curl \
    libfreetype6-dev \
    libjpeg62-turbo-dev \
    libmcrypt-dev \
    libpng-dev && \
    pecl install redis && \
    docker-php-ext-enable redis
RUN docker-php-ext-install bcmath
RUN docker-php-ext-install pdo_mysql mysqli exif
RUN cd /usr/bin && curl -s http://getcomposer.org/installer | php && ln -s /usr/bin/composer.phar /usr/bin/composer

COPY ./container/api/php.ini /usr/local/etc/php/php.ini

# 非ルートユーザーに切り替え
RUN useradd -ms /bin/bash php && \
    echo "php:php" | chpasswd

USER php

###################################
#
#   本番環境
#
###################################

FROM php:8.4-fpm-alpine AS production

WORKDIR /home/php/api

COPY --chown=php:php ./api /home/php/api

# COPY --from=dev --chown=php:php composer*.json ./
COPY --from=dev /usr/bin/composer /usr/bin/composer
COPY --from=dev /usr/local/etc/php/php.ini /usr/local/etc/php/php.ini
RUN composer install --no-scripts --no-autoloader

# 非ルートユーザーに切り替え
RUN addgroup -S -g "1000" "php" && \
    adduser -u "1000" -G "php" -D "php"

USER php

VOLUME ["/var/run/php-fpm"]

CMD ["php-fpm", "-R"]
zend.exception_ignore_args = off
expose_php = on
max_execution_time = 30
max_input_vars = 1000
upload_max_filesize = 64M
post_max_size = 128M
memory_limit = 256M
error_reporting = E_ALL
display_errors = on
display_startup_errors = on
log_errors = on
error_log = /var/log/php/php-error.log
default_charset = UTF-8

[Date]
date.timezone = Asia/Tokyo

[mysqlnd]
mysqlnd.collect_memory_statistics = on

[Assertion]
zend.assertions = 1

[mbstring]
mbstring.language = Japanese

結果

大体1/6となりました。本番用の環境をalpineベースのイメージを使用してるため、当たり前と言われたら当たり前なのですが。(-_-;)

test-multistage-build-api     latest    2fb32a6411af   18 seconds ago   148MB

念のため、Trivyを使用して脆弱性をチェック

参考記事より

Trivy は、Aqua Security社が提供するオープンソースの脆弱性スキャナです。コンテナイメージ、ファイルシステム、Kubernetes、リポジトリなどに潜む脆弱性を迅速かつ簡単に検出することができます。

Trivyの特徴としては、以下が挙げられます:

  • 多彩なスキャン対象: Dockerイメージやファイルシステム、Kubernetesクラスタ、IaC(Infrastructure as Code)などに対応
  • 高精度な脆弱性検出: 詳細な脆弱性データベース(NVDやRed Hat、Ubuntuなど)を活用し、精度の高いスキャンを実現
  • 簡単なセットアップ: 初期設定が非常に簡単で、短時間でスキャンを実行可能

実行方法

docker run --rm -v /var/run/docker.sock:/var/run/docker.sock aquasec/trivy image イメージ名

結果

本来は表のような出力結果が表示されますが、端折ってます。

nodejsコンテナ

  • @babel/runtime (package.json)│CVE-2025-27789│MEDIUM│fixed│7.22.5

phpコンテナ

  • sqlite-libs│CVE-2025-29087│MEDIUM│fixed│3.48.0-r0│ 3.48.0-r1

今回の構築を通して、Linuxの特にユーザーや権限等、今回の環境であるnodejs, phpなどの理解が必要であると再認識できました。やはりLinuxがないと今後Dockerが活かせないので今後本等を読んで知識を深めていきたい。

参考

最後に

間違っていることやセキュリティ上のベストプラクティスでこういう書き方の方が良いよなどあればコメントに書いていただけると幸いです。
よろしくお願いいたします。

GitHubで編集を提案

Discussion