tl;dr
- Next.jsのSSG(Static Site Generator)を利用したDocker Imageを作る際にMulti Stage BuildでImageを作ると、利用しない場合に比べて453MB変わる
Dockerfile
以前とは違い、Production環境でも利用出来るように構築をした。SSGを利用する前提で書いている。 ただ、実際にはSSGをProductionで利用する場合はNetlifyやVercel、S3など静的コンテンツを置くのに適したプラットフォームを利用すると思うので、あまり意味はないかもしれない。
それぞれbase
, builder
, production
とステージを分けている。
base
依存関係にあるnpm packageをインストールする。開発環境ではbase stageを利用し、docker build時の引数でnext
コマンドを指定するだけで良い。
$ docker build -t next-app:base --target base . Sending build context to Docker daemon 177.8MB Step 1/7 : FROM node:14-alpine as base ---> fa2fa5d4e6f4 Step 2/7 : WORKDIR /app/js/base ---> Using cache ---> 3bcd585979e7 Step 3/7 : COPY package.json . ---> Using cache ---> 73e62066ac85 Step 4/7 : COPY yarn.lock . ---> Using cache ---> d6ed6b01b3ed Step 5/7 : COPY tsconfig.json . ---> Using cache ---> 7a8ae367da60 Step 6/7 : RUN yarn install ---> Using cache ---> dd874aced5fe Step 7/7 : COPY . . ---> Using cache ---> 5ad130f7577a Successfully built 5ad130f7577a Successfully tagged next-app:base $ docker run -it --rm -p 3000:3000 next-app:base yarn next yarn run v1.22.5 $ /app/js/base/node_modules/.bin/next ready - started server on http://localhost:3000 info - ready on http://localhost:3000 event - build page: / wait - compiling... A codemod is available to fix the most common cases: https://nextjs.link/codemod-ndc info - ready on http://localhost:3000
builder
アプリケーションをbuildする層。この層でアプリケーションbuildしproduction stageへは不要なもの(node_modules, ソースコード etc)を持ち込まないようにする。
production
Production環境に適応したイメージを作る層。ここで不要なファイルが作成されるとその分Imageサイズが大きくなるので不要なものは入れてはいけない。 このstageでbuildをしてしまうとソースコードなど不要なものが必要になるので、一つ前のbuilder stageから必要なものをコピーする。
最後のyarn install --production
でproductionに必要な依存関係のみをインストールする。
結果
Multi Stage Buildを利用した場合と利用しない場合で比較をしてみる。
Multi Stage Buildを利用する
$ docker build -t next-app:base . --target base Sending build context to Docker daemon 194.2MB Step 1/7 : FROM node:14-alpine as base ---> fa2fa5d4e6f4 Step 2/7 : WORKDIR /app/js/base ---> Using cache ---> 7a04d844b125 Step 3/7 : COPY package.json . ---> Using cache ---> ab5127932992 Step 4/7 : COPY yarn.lock . ---> Using cache ---> e8ba5b6cb1ab Step 5/7 : COPY tsconfig.json . ---> Using cache ---> 10b359198a18 Step 6/7 : RUN yarn install ---> Using cache ---> affbace5d646 Step 7/7 : COPY . . ---> Using cache ---> ce9e93809dc2 Successfully built ce9e93809dc2 Successfully tagged next-app:base
builder stage
$ docker build -t next-app:builder . --target builder Sending build context to Docker daemon 194.2MB Step 1/12 : FROM node:14-alpine as base ---> fa2fa5d4e6f4 Step 2/12 : WORKDIR /app/js/base ---> Running in ad2141be97fb Removing intermediate container ad2141be97fb ---> 6d182a28bf59 Step 3/12 : COPY package.json . ---> 4a44d0936af9 Step 4/12 : COPY yarn.lock . ---> 489f697cba57 Step 5/12 : COPY tsconfig.json . ---> b155046ac03c Step 6/12 : RUN yarn install ---> Running in 937d5fb38b0e yarn install v1.22.5 [1/4] Resolving packages... [2/4] Fetching packages... info fsevents@1.2.13: The platform "linux" is incompatible with this module. info "fsevents@1.2.13" is an optional dependency and failed compatibility check. Excluding it from installation. info fsevents@2.1.3: The platform "linux" is incompatible with this module. info "fsevents@2.1.3" is an optional dependency and failed compatibility check. Excluding it from installation. [3/4] Linking dependencies... warning " > file-loader@6.0.0" has unmet peer dependency "webpack@^4.0.0 || ^5.0.0". warning " > url-loader@4.1.0" has unmet peer dependency "webpack@^4.0.0 || ^5.0.0". [4/4] Building fresh packages... Done in 49.13s. Removing intermediate container 937d5fb38b0e ---> 27a2d7a30d34 Step 7/12 : COPY . . ---> c19dc009a6a6 Step 8/12 : FROM node:14-alpine as builder ---> fa2fa5d4e6f4 Step 9/12 : ENV NODE_ENV=production ---> Running in 3bd3364115b5 Removing intermediate container 3bd3364115b5 ---> 0cfcd40518b3 Step 10/12 : WORKDIR /app/js/builder ---> Running in 0e20dd40600f Removing intermediate container 0e20dd40600f ---> 9dac22ae0e11 Step 11/12 : COPY --from=base /app/js/base /app/js/builder ---> 208200318cf4 Step 12/12 : RUN ["yarn", "build"] ---> Running in ef55ba375411 yarn run v1.22.5 $ next build && next export Creating an optimized production build... Compiled successfully. Automatically optimizing pages... Page Size First Load JS ┌ ● / 9.31 kB 90.9 kB ├ /_app 1.9 kB 60 kB ├ ○ /404 3.25 kB 63.2 kB └ ● /articles/[id] 344 B 81.9 kB ├ /articles/1 └ /articles/2 + First Load JS shared by all 60 kB ├ static/pages/_app.js 1.9 kB ├ chunks/444ecc389d1b2833cd1c90b9f6d2e962a7bb15f6.c2e7b8.js 10.7 kB ├ chunks/62e1b0f2.e05b9c.js 64 B ├ chunks/framework.4dd100.js 40.3 kB ├ runtime/main.9f98e5.js 6.28 kB ├ runtime/webpack.c21266.js 746 B ├ css/7ed58071346528c353bf.css 97.4 kB └ css/cfb837a2694c3ad390b0.css 108 B λ (Server) server-side renders at runtime (uses getInitialProps or getServerSideProps) ○ (Static) automatically rendered as static HTML (uses no initial props) ● (SSG) automatically generated as static HTML + JSON (uses getStaticProps) > using build directory: /app/js/builder/.next copying "static build" directory > No "exportPathMap" found in "next.config.js". Generating map from "./pages" launching 1 workers Exporting (0/2) copying "public" directory Exporting (1/2) Exporting (2/2) Export successful Done in 42.64s. Removing intermediate container ef55ba375411 ---> ef487ea233e3 Successfully built ef487ea233e3 Successfully tagged next-app:builder
production stage
$ docker build -t next-app:production . Sending build context to Docker daemon 194.2MB Step 1/18 : FROM node:14-alpine as base ---> fa2fa5d4e6f4 Step 2/18 : WORKDIR /app/js/base ---> Running in 141707b10d1b Removing intermediate container 141707b10d1b ---> 7a04d844b125 Step 3/18 : COPY package.json . ---> ab5127932992 Step 4/18 : COPY yarn.lock . ---> e8ba5b6cb1ab Step 5/18 : COPY tsconfig.json . ---> 10b359198a18 Step 6/18 : RUN yarn install ---> Running in 25c5e101426f yarn install v1.22.5 [1/4] Resolving packages... [2/4] Fetching packages... info fsevents@1.2.13: The platform "linux" is incompatible with this module. info "fsevents@1.2.13" is an optional dependency and failed compatibility check. Excluding it from installation. info fsevents@2.1.3: The platform "linux" is incompatible with this module. info "fsevents@2.1.3" is an optional dependency and failed compatibility check. Excluding it from installation. [3/4] Linking dependencies... warning " > file-loader@6.0.0" has unmet peer dependency "webpack@^4.0.0 || ^5.0.0". warning " > url-loader@4.1.0" has unmet peer dependency "webpack@^4.0.0 || ^5.0.0". [4/4] Building fresh packages... Done in 78.52s. Removing intermediate container 25c5e101426f ---> affbace5d646 Step 7/18 : COPY . . ---> ce9e93809dc2 Step 8/18 : FROM node:14-alpine as builder ---> fa2fa5d4e6f4 Step 9/18 : ENV NODE_ENV=production ---> Running in 05b08f765682 Removing intermediate container 05b08f765682 ---> 43348a423c2a Step 10/18 : WORKDIR /app/js/builder ---> Running in e924b7124b7e Removing intermediate container e924b7124b7e ---> d872eca49f96 Step 11/18 : COPY --from=base /app/js/base /app/js/builder ---> 5aa288be27aa Step 12/18 : RUN ["yarn", "build"] ---> Running in c1762e58a430 yarn run v1.22.5 $ next build && next export Creating an optimized production build... Compiled successfully. Automatically optimizing pages... Page Size First Load JS ┌ ● / 9.31 kB 90.9 kB ├ /_app 1.9 kB 60 kB ├ ○ /404 3.25 kB 63.2 kB └ ● /articles/[id] 344 B 81.9 kB ├ /articles/1 └ /articles/2 + First Load JS shared by all 60 kB ├ static/pages/_app.js 1.9 kB ├ chunks/62e1b0f2.e05b9c.js 64 B ├ chunks/9a9e7c821a17e6790c99026b07c1c46382c721c2.c2e7b8.js 10.7 kB ├ chunks/framework.4dd100.js 40.3 kB ├ runtime/main.9f98e5.js 6.28 kB ├ runtime/webpack.c21266.js 746 B ├ css/7ed58071346528c353bf.css 97.4 kB └ css/cfb837a2694c3ad390b0.css 108 B λ (Server) server-side renders at runtime (uses getInitialProps or getServerSideProps) ○ (Static) automatically rendered as static HTML (uses no initial props) ● (SSG) automatically generated as static HTML + JSON (uses getStaticProps) > using build directory: /app/js/builder/.next copying "static build" directory > No "exportPathMap" found in "next.config.js". Generating map from "./pages" launching 1 workers Exporting (0/2) copying "public" directory Exporting (1/2) Exporting (2/2) Export successful Done in 51.92s. Removing intermediate container c1762e58a430 ---> d264d497a0a5 Step 13/18 : FROM nginx:latest as production ---> f35646e83998 Step 14/18 : WORKDIR /app/js/src ---> Running in 89ce96f55a15 Removing intermediate container 89ce96f55a15 ---> fd0fb921aa75 Step 15/18 : RUN rm -rf /etc/nginx/conf.d ---> Running in d73698e7006d Removing intermediate container d73698e7006d ---> 85cc19c895e4 Step 16/18 : ADD conf/nginx/conf.d /etc/nginx/conf.d ---> 27ee1c3cfac8 Step 17/18 : COPY --from=builder /app/js/builder/out ./out ---> 5d4d1bd4881e Step 18/18 : EXPOSE 80 ---> Running in ef6862c2ccd5 Removing intermediate container ef6862c2ccd5 ---> 46fc04190def Successfully built 46fc04190def Successfully tagged next-app:production
Multi Stage Buildを利用しない
$ cat Dockerfile_not_msb FROM alpine:latest RUN apk update RUN apk add --update nodejs nodejs-npm yarn WORKDIR /app/js COPY package.json . COPY yarn.lock . COPY tsconfig.json . RUN yarn install COPY . . RUN ["yarn", "build"] RUN apk add nginx openrc RUN rm -rf /etc/nginx/conf.d ADD conf/nginx/conf.d /etc/nginx/conf.d EXPOSE 80 $ docker build -t next-app:not_msb -f Dockerfile_not_msb . Sending build context to Docker daemon 224MB Step 1/14 : FROM alpine:latest ---> a24bb4013296 Step 2/14 : RUN apk update ---> Using cache ---> 5a65f08f5db7 Step 3/14 : RUN apk add --update nodejs nodejs-npm yarn ---> Using cache ---> c97ad0a3e846 Step 4/14 : WORKDIR /app/js ---> Using cache ---> a37951715a8d Step 5/14 : COPY package.json . ---> Using cache ---> 6faddfc45d20 Step 6/14 : COPY yarn.lock . ---> Using cache ---> bd166cc22fbc Step 7/14 : COPY tsconfig.json . ---> Using cache ---> f827439198ba Step 8/14 : RUN yarn install ---> Using cache ---> 46c450c588da Step 9/14 : COPY . . ---> Using cache ---> 7b18d341a97a Step 10/14 : RUN ["yarn", "build"] ---> Using cache ---> 578c525c3ce1 Step 11/14 : RUN apk add nginx openrc ---> Using cache ---> 94434ea28089 Step 12/14 : RUN rm -rf /etc/nginx/conf.d ---> Using cache ---> 571eedb7cbb5 Step 13/14 : ADD conf/nginx/conf.d /etc/nginx/conf.d ---> Using cache ---> 1a765adec9ca Step 14/14 : EXPOSE 80 ---> Using cache ---> 5df8f8cc2b74 Successfully built 5df8f8cc2b74 Successfully tagged next-app:not_msb
結果
$ docker images | grep next-app next-app not_msb 5df8f8cc2b74 7 minutes ago 614MB next-app builder ef487ea233e3 About an hour ago 327MB next-app production 46fc04190def About an hour ago 161MB next-app base ce9e93809dc2 About an hour ago 599MB
まとめ
- Multi Stage Buildを利用することで一つのDockerfileで開発環境・Production環境をすぐに作ることが出来る。
- Multi Stage Buildを利用した場合、利用しない場合に比べて453MB変わる。