Nest.js製アプリのDockerイメージサイズをできる限り小さくする

こんにちは。HRBrainでSREをしている桜庭です。 この記事は HRBrain AdventCalender 17日目の記事です。

今日はこの記事で紹介した組織分析のDockerイメージを pkg を使ってどのようにして圧縮していったのかを紹介したいと思います。

times.hrbrain.co.jp

Before

まずは改善前のDockerfileから。

FROM node:12-stretch-slim

WORKDIR /app
COPY yarn.lock /app/yarn.lock
COPY package.json /app/package.json
RUN yarn install

WORKDIR /app
ENV NODE_ENV=production
COPY . /app
RUN yarn build

EXPOSE 3500
CMD ["node", "./dist/main.js"]

一般的なDockerfileです。

もちろんこれでも動作しますし問題はないのですが、サイズを見てみると 958MB かなり大きめで docker pushdocker pull の速度に影響しそうです...

After

お次は改善後。

FROM node:12-stretch-slim AS builder

RUN yarn global add pkg

WORKDIR /app
COPY package.json /app/package.json
COPY yarn.lock /app/yarn.lock
RUN yarn install

COPY . /app
ENV NODE_ENV=production
RUN yarn build && \
    pkg --target node12-linux --output bin ./dist/main.js

FROM node:12-stretch-slim AS native-builder
WORKDIR /app
RUN yarn add grpc

FROM debian:stretch-slim
WORKDIR /app
COPY --from=builder /app/bin /app/bin
COPY --from=native-builder /app/node_modules /app/node_modules

EXPOSE 3500
CMD ["/bin"]

このように大胆に書き換えたところイメージのサイズは213MB !なんと1/5 程度まで小さくすることができました。

どうしてそこまで削れるのかを次から説明していきます。

vercel/pkg を使ってアプリケーションを一つのバイナリにする

まず最初にdependenciesの中にはビルド時には必要だが実行時に不要な物も多くあるのでそれを削ることはできないか考えました。で、いろいろ考えた結果シングルバイナリにまとめれば不要な物を削れるのでは?と考えpkg を使うことにしました。

pkg はNext.jsなどで有名なVercel社が開発しているOSSで、Node.jsのプロジェクトを実行可能な一つのバイナリにすることができます。

これを使用してアプリケーションをバイナリにまとめることによって実行時に必要のない依存関係がイメージに含まれなくなり、大幅にイメージサイズの削減をすることができました。

vercel/pkg を使う時の注意点

大幅なファイルサイズ削減に成功しましたがいくつか注意すべき点はあります。

リポジトリのREADMEにも書かれていることですがファイルシステム周りの挙動が普通に実行したときと異なります。 特に process.argv[0], process.argv[1] などはCLIを作るときによく使うので注意が必要でしょう。

またネイティブモジュール(gRPC)などを使うときも同様です。

Dockerでビルドするときは以下のように別途ネイティブモジュールのみをインストールして node_modules をコピーするなどちょっとした対策が必要になります。

FROM node:12-stretch-slim AS builder

# 省略

FROM node:12-stretch-slim AS native-builder
WORKDIR /app
RUN yarn add grpc

FROM debian:stretch-slim
# 省略
COPY --from=native-builder /app/node_modules /app/node_modules

まとめ

今回使った vercel/pkg によるNest.jsアプリケーションのシングルバイナリ化ですが、Nest.jsに限らず他のNode.jsプロジェクトでももちろん動作します。個人的にはシングルバイナリにできるならTypeScriptでCLIを作って配布するのが楽しそうだなーと思っています。

書いてる最中に NODE_ENV=production yarn install とすれば devDependencies がインストールされないからもっと小さくできるのでは?と気づいてしまったのは秘密です。

以上、なかなかニッチな内容ですがお役に立てれば幸いです。