バックエンドエンジニアは、Dockerに触れる機会が多いと思います。Dockerを使用するにあたり必ずと言っていいほど必要になってくるのが、Dockerfileですね。 なんとなくDockerfileを使用していると気づかないうちにDockerイメージが大きくなってしまい、イメージの容量でPCがいっぱいいっぱいになってしまいます。 せっかくなのでこの記事を読んでDockerイメージを軽くしましょう!

目次

Dockerイメージの容量を減らすことによるメリット

  • PCなどのディスク容量を圧迫しない
  • GCPやAWSなどのストレージ系料金を抑えられる
  • Deploy時間を短縮できる

Dockerイメージ小さくしたくなりましたか?したくなりましたね。 Dockerイメージを削減する方法で、COPYRUNなどの記載を少なくし、レイヤーを削減する方法がありますが、
今回は一番効果の大きいマルチステージビルドを利用した方法を説明します。

まずは、公式のドキュメントです。
公式: マルチステージビルドの利用

んーわたし的にわかりやすく説明しようと思います。

マルチステージビルドの文法は Docker Engine 17.05 から導入されています。

例を記載しながら説明します。
今回の例はnpm, typescript, expressで記載されたバックエンドサーバーのプロジェクトとします。
package.jsonの例(他に多くのパッケージがありますが、省略しています)

{
  "name": "example",
  "scripts": {
    "build": "rm -rf dist && tsc",
    "start": "node dist/index.js",
  },
  "dependencies": {
    "express": "^4.18.1",
    ...
  },
  "devDependencies": {
    "typescript": "^4.6.4",
    ...
  }
}

まず、マルチステージビルドを使用しないでDockerfileを記載してみます。

single stage build

FROM node:16-slim

ENV APP_ROOT /app/
WORKDIR $APP_ROOT

COPY . $APP_ROOT

RUN npm ci && npm run build

CMD npm start

よく、Qiitaの例であるやつですね。


次にマルチステージビルドを使用した例です。
バックエンドサービスとして動く動作はもちろん同じです。

multi stage build 1

# ------------------------
# First stage: builder
# ------------------------
FROM node:16-slim AS builder

ENV HOME=/build
WORKDIR $HOME

COPY . $HOME
RUN npm ci && npm run build

# ------------------------
# Second stage: release
# ------------------------
FROM node:16-slim AS release

ENV HOME=/app
WORKDIR $HOME

COPY package* $HOME/
COPY --from=builder /build/dist $HOME/dist

RUN npm ci

CMD npm start

First stageで全体をコピーし、ビルド(tsc)を実施しています。
次にSecond stageで必要なファイルのみFirst stageからコピーしています。
実際にサーバーが動くために必要なファイルは、コマンド実行とnode_modules作成のためのpackage.json(package.lock.json)とビルドした結果のdistだけですよね?


まだ無駄なところがあります。npm ciをするときにdevDependenciesに記載されているパッケージもインストールされていますね。しかし実際にサーバーとして動かすためにdevDependenciesに記載されているものは必要ないので、無駄に容量が大きくなってしまいます。
必要なパッケージだけインストールするようにしましょう npm ci --production

multi stage build 2

# ------------------------
# First stage: builder
# ------------------------
FROM node:16-slim AS builder

ENV HOME=/build
WORKDIR $HOME

COPY . $HOME
RUN npm ci && npm run build

# ------------------------
# Second stage: release
# ------------------------
FROM node:16-slim AS release

ENV HOME=/app
WORKDIR $HOME

COPY package* $HOME/
COPY --from=builder /build/dist $HOME/dist

RUN npm ci --production

CMD npm start

いい感じになりましたね。 実際にできたイメージを比較してみましょう

docker-image-compare single-stage-buildとmulti-stage-build2で比べると、約1/6になりました。ここまで変わるとは…
この結果を見るにやらない理由がない! フロントエンドなら動作にnode_modulesが必要ないのでさらにイメージが小さくなりそうですね。

以上で検証を終わりにしたいと思います。

PCへの負担もクラウドの料金もビルド時間も少ない方がいいですよね