12
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

【NestJS】マルチステージビルドでNestJSイメージのサイズを小さくする方法

Last updated at Posted at 2022-08-27

はじめに

Dockerイメージのサイズを見ちゃったりしてると玄人感出ますよね...
『目指せ、玄人!』ということで、NestJSでのマルチステージビルドについてまとめます📝

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

ざっくりいうと「ビルドするステージとランタイムで使用するステージを分けて記述する方式」です。
これにより、Dockerイメージを軽量化することができます!
公式

結論

私が考えたNestJS用のdockerfileはこちらです↓↓

FROM node:16.17.0 AS dist

WORKDIR /tmp

COPY ./app ./

RUN yarn install \
    && yarn build


FROM node:16.17.0 AS module

WORKDIR /tmp

COPY ./app ./

RUN yarn install --prod


FROM node:16.17.0-alpine

WORKDIR /usr/app

COPY --from=dist /tmp/dist ./dist
COPY --from=module /tmp/node_modules ./node_modules

EXPOSE 3000

CMD ["node", "dist/main"]

最終的に生成されたコンテナには、distnode_modulesしか存在しません。


ここから順を追って解説

ディレクトリ構成

$ tree -L 2
.
├── README.md
├── app
│   ├── README.md
│   ├── nest-cli.json
│   ├── node_modules
│   ├── package.json
│   ├── src
│   ├── test
│   ├── tsconfig.build.json
│   ├── tsconfig.json
│   └── yarn.lock
└── dockerfile

nest new appを実行して、dockerfileを配置しただけの状態からスタートします💨
NestJSを立ち上げて、localhost:3000にアクセスすると、Hello World!と返ってきます。

dockerfile1

とりあえず動くコンテナ編

dockerfile1
FROM node:16.17.0

WORKDIR /usr/app

COPY ./app ./

RUN yarn install
RUN yarn build

EXPOSE 3000

CMD ["yarn", "start:prod"]

めちゃくちゃよくありそうなdockerfile。こちらでもちゃんとNestJSは動作します😺

イメージ確認1

$ docker build -t image:1 .
$ docker images
REPOSITORY   TAG       IMAGE ID       CREATED          SIZE
image        1         6d8ba6caed1c   46 seconds ago   1.49GB
$ docker run --name nest -p3000:3000 --rm -d image:1
beb048664d49cf9997d85ead5844859d72e98dd73fc8f6b68fb6d9ed350ffe78
$ curl http://localhost:3000
Hello World!

※ 以後、curlでの確認は割愛します🙇‍♂️

一応動きますが、1.49GBという激重コンテナが爆誕したわけです💣
ディレクトリ配下のすべての資源をDockerイメージに含めていることが主な原因です。コンテナの中を覗いてみると、ランタイムではいならい資源がたくさんあることがわかります。

$ docker run --rm image:1 ls
README.md
dist
nest-cli.json
node_modules
package.json
src
test
tsconfig.build.json
tsconfig.json
yarn.lock

ここから軽量化に向けて、少しずつ改善していこうと思います🤸‍♀️

dockerfile2

マルチステージビルド編

dockerfile2
FROM node:16.17.0 AS build

WORKDIR /tmp

COPY ./app ./

RUN yarn install
RUN yarn build


FROM node:16.17.0

WORKDIR /usr/app

COPY --from=build /tmp/dist ./dist
COPY --from=build /tmp/package.json ./
COPY --from=build /tmp/node_modules ./node_modules

EXPOSE 3000

CMD ["yarn", "start:prod"]

出ました!マルチステージビルド🐳
NestJSのビルドのステージとランタイム実行用のステージを分離して記述しています。AS buildでステージに名前をつけることができ、--from=buildでそのステージを参照することができます!

イメージ確認2

$ docker images
REPOSITORY   TAG       IMAGE ID       CREATED          SIZE
image        2         ecaa05bb4a9a   2 minutes ago    1.2GB

お!!少しだけですが、軽くなりました!(1.49GB1.2GB)
少ししか軽くなっていないと感じるかもしれませんが、アプリケーション開発が進むと、srctestの容量がすぐに増えて、マルチステージビルドをしていないとどんどんDockerイメージが大きくなります。

dockerfile3

prod用のnode_modulesを作成して、package.jsonすらコンテナに含めないようにする編

dockerfile3
FROM node:16.17.0 AS dist

WORKDIR /tmp

COPY ./app ./

RUN yarn install \
    && yarn build


FROM node:16.17.0 AS module

WORKDIR /tmp

COPY ./app ./

RUN yarn install --prod


FROM node:16.17.0

WORKDIR /usr/app

COPY --from=dist /tmp/dist ./dist
COPY --from=module /tmp/node_modules ./node_modules

EXPOSE 3000

CMD ["node", "dist/main"]

yarn install--prodオプションをつけると、devDependenciesのモジュールをnode_modulesに含めないという挙動をとります。
ステージを2つに分けて、それぞれの成果物をランタイム用のコンテナに配置することで、より軽量なコンテナを作成することができます!
ついでに、package.jsonのコマンドをCMDに直接書くようにしました。そうすることで、ランタイムではpackage.jsonがいらなくなります。

注意

以下に--prodオプションを付ければいいと思うかもしれませんが、そうするとyarn buildでコケます...
NestJSのcliは、devDependenciesに存在しているためです。

RUN yarn install \
    && yarn build

イメージ確認1

$ docker images
REPOSITORY   TAG       IMAGE ID       CREATED             SIZE
image        3         a410579410f9   29 seconds ago      921MB

だいぶ軽くなりましたね!(1.2GB921MB)
node_modulesdevDependenciesがいかに重いかがよくわかります💥

dockerfile4(おまけ)

そもそも軽いnodeイメージを使うべき編

dockerfile4
FROM node:16.17.0 AS dist

WORKDIR /tmp

COPY ./app ./

RUN yarn install \
    && yarn build


FROM node:16.17.0 AS module

WORKDIR /tmp

COPY ./app ./

RUN yarn install --prod


FROM node:16.17.0-alpine

WORKDIR /usr/app

COPY --from=dist /tmp/dist ./dist
COPY --from=module /tmp/node_modules ./node_modules

EXPOSE 3000

CMD ["node", "dist/main"]

nodeイメージはdebian系の結構重めのイメージなので、ランタイムではもっと軽量なイメージを使いましょう!
用途に合わせて軽いイメージを選定してもらえるといいかなと思います!

REPOSITORY   TAG                   IMAGE ID       CREATED             SIZE
node         16.17.0-alpine        5dcd1f6157bd   9 days ago          115MB
node         16.17.0-buster-slim   af57506e8951   3 days ago          179MB
node         16.17.0-slim          af57506e8951   3 days ago          179MB
node         16.17.0               c8af85aa3027   3 days ago          910MB

また、マルチステージビルドのイメージ(node)は、軽量のものを使うかはおまかせします。最終的には破棄されるので、Dockerイメージのサイズには影響しません。ただ、イメージのpullに時間はかかるので、できる限り軽量のものを使うことをおすすめします。

動作確認4

$ docker images
REPOSITORY   TAG       IMAGE ID       CREATED             SIZE
image        4         91483413dff1   2 minutes ago       125MB

マルチステージビルドの前にこれやりなよって話ですね...(921MB125MB)
bashすら入っていない超軽量イメージなので、コンテナの中に入って色々やりたい時はちょっと困るかもしれません。

まとめ

マルチステージビルドによって、Dockerイメージを軽量化することができました!
マルチステージビルドの導入によって、1.49GB921MBになりました!
イメージが軽くなると、ECRへのpush、ECSのデプロイが早くなるので開発速度が上げられると思います!
この機会に皆さんのdockerfileを見直してみてください🙇‍♂️

12
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?