- vừa được xem lúc

Tối ưu Docker Image cho NextJS

0 0 19

Người đăng: Trần Đức

Theo Viblo Asia

Docker image của NextJS thông thường size sẽ rất lớn. Bài viết này mình sẽ trình bày 2 cách mình hay sử dụng để tối ưu image trước khi triển khai lên môi trường production.

Full Source code in here

Để có thể theo dõi 1 cách trực quan, mình sẽ demo theo 3 kịch bản như sau:

  • Sử dụng cách cơ bản để viết dockerfile.
  • Áp dụng Multi Stage.
  • Sử dụng mode standalone của NextJS kết hợp với Multi Stage.

Lưu ý: Mình sẽ không tập trung nhiều về các cách cơ bản (như hạn chế tạo nhiều Layer, .dockerignore, ...) để tối ưu image, mà sẽ trình bày sâu vào 2 cách chính đã đề cập ở trên. Các bạn có thể theo dõi bài viết Tối ưu Docker image của anh Mai Trung Đức để tham khảo thêm các cách để tối ưu docker image rất hay và chi tiết 😄

Oke !!! Vào thôi

Setup

Đầu tiên, chúng ta cần 1 project sử dụng NextJS, ở đây để cho nhanh mình sẽ sài luôn cái blog-starter trong phần examples của NextJS. Các bạn có thể clone ở đây về nha 😄

Cấu trúc của nó trông như này:

.
├── @types
│ └── remark-html.d.ts
├── README.md
├── _posts
│ ├── dynamic-routing.md
│ ├── hello-world.md
│ └── preview.md
├── components
│ ├── alert.tsx
│ ├── avatar.tsx
│ ├── container.tsx
│ ├── cover-image.tsx
│ ├── date-formatter.tsx
│ ├── footer.tsx
│ ├── header.tsx
│ ├── hero-post.tsx
│ ├── intro.tsx
│ ├── layout.tsx
│ ├── markdown-styles.module.css
│ ├── meta.tsx
│ ├── more-stories.tsx
│ ├── post-body.tsx
│ ├── post-header.tsx
│ ├── post-preview.tsx
│ ├── post-title.tsx
│ └── section-separator.tsx
├── interfaces
│ ├── author.ts
│ └── post.ts
├── lib
│ ├── api.ts
│ ├── constants.ts
│ └── markdownToHtml.ts
├── next-env.d.ts
├── package.json
├── pages
│ ├── _app.tsx
│ ├── _document.tsx
│ ├── index.tsx
│ └── posts
│ └── [slug].tsx
├── postcss.config.js
├── public
│ ├── assets
│ │ └── blog
│ │ ├── authors
│ │ │ ├── jj.jpeg
│ │ │ ├── joe.jpeg
│ │ │ └── tim.jpeg
│ │ ├── dynamic-routing
│ │ │ └── cover.jpg
│ │ ├── hello-world
│ │ │ └── cover.jpg
│ │ └── preview
│ │ └── cover.jpg
│ └── favicon
│ ├── android-chrome-192x192.png
│ ├── android-chrome-512x512.png
│ ├── apple-touch-icon.png
│ ├── browserconfig.xml
│ ├── favicon-16x16.png
│ ├── favicon-32x32.png
│ ├── favicon.ico
│ ├── mstile-150x150.png
│ ├── safari-pinned-tab.svg
│ └── site.webmanifest
├── styles
│ └── index.css
├── tailwind.config.js
└── tsconfig.json

Okie, sau đấy cài và chạy thử lên xem phát :v

➜ blog-starter git:(master) ✗ yarn
➜ blog-starter git:(master) ✗ yarn build
➜ blog-starter git:(master) ✗ yarn start

Các bạn truy cập vào localhost:3000 để xem qua nhé :3

Trông cũng ổn đấy :v, ô kê vào phần chính nào ...

Build on Docker

Đầu tiên thì mình sẽ nhét những files hay folders không cần thiết vào .dockerignore:

node_modules
.next
.vscode
*.DS_Store
.gitignore
README.md
.dockerignore
LICENSE
.docker
.gitlab

Như đã đề cập ở trên mình sẽ trình bày 3 kịch bản. Bây giờ, mình sẽ sử dụng cách tạo cơ bản trước theo basic.dockerfile:

FROM node:16-alpine
LABEL author="ductnn <_@.com>" WORKDIR /app COPY package.json yarn.lock ./
RUN apk add --no-cache git \ && yarn install --frozen-lockfile \ && yarn cache clean COPY . .
RUN yarn build EXPOSE 3000 CMD ["yarn", "start"]

Tiến hành build:

➜ blog-starter git:(master) ✗ docker build -t blog-with-basic-dockerfile -f .docker/basic.dockerfile .

# Check docker images
➜ blog-starter git:(master) ✗ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
blog-with-basic-dockerfile latest b70f75178890 8 seconds ago 370MB

Ở cách build này, image tạo ra có kích thước là 370MB. Trên thực tế, dự án của chúng ta sẽ cần nhiều packages hơn nên với cách build này chúng ta không thể tối ưu được nhiều.

Tiếp theo, mình sẽ dùng Multi stage để build dockerfile. Ở đây, mình chia thành 3 stagebase, build, production:

# Build BASE
FROM node:16-alpine as BASE
LABEL author="ductnn <_@.com>" WORKDIR /app
COPY package.json yarn.lock ./
RUN apk add --no-cache git \ && yarn install --frozen-lockfile \ && yarn cache clean # Build Image
FROM ductn4/node:16-alpine AS BUILD
LABEL author="ductnn <_@.com>" WORKDIR /app
COPY --from=BASE /app/node_modules ./node_modules
COPY . .
RUN apk add --no-cache git curl \ && yarn build \ && rm -rf node_modules \ && yarn install --production --frozen-lockfile --ignore-scripts --prefer-offline \ # Follow https://github.com/ductnn/Dockerfile/blob/master/nodejs/node/16/alpine/Dockerfile && node-prune # Build production
FROM node:16-alpine AS PRODUCTION
LABEL author="ductnn <_@.com>" WORKDIR /app COPY --from=BUILD /app/package.json /app/yarn.lock ./
COPY --from=BUILD /app/node_modules ./node_modules
COPY --from=BUILD /app/.next ./.next
COPY --from=BUILD /app/public ./public EXPOSE 3000 CMD ["yarn", "start"]

stage: base mình tiền hành install package.json, mục đích là để lấy node_modules phục vụ cho stage: build. Trong có trình thực hiện cài đặt mình có dùng thêm --frozen-lockfile để "đóng băng" packages.

Tiếp theo, stage: buildstage quan trọng nhất. Bước này, mình sẽ copy node_modulesstage: basesource code xong tiến hành yarn build. Sau khi build xong mình sẽ xoá node_modules đi và ... cài lại 😅 ủa để làm chi ??? Có 1 chút khác mình sử dụng thêm --production với mục đích chỉ cài các packages cần thiết trong packeage.json và bỏ qua các packages trong phần devDependencies. Điều này sẽ giúp giảm nhẹ hơn node_modules rất nhiều,

node_modules

mình không cần lo chạy lỗi vì mình đã build đầy đủ từ bước trên rồi. Nhưng mình vẫn muốn thằng node_modules nhẹ hơn ...

Okies !!! Tiếp đó, mình sử dụng 1 chiếc tool để làm nhẹ đi node_modules tên là node-prune, trong base node imagestage build mình đã cài sẵn node-prune, anh em có thể tham khảo cách cài đặt tại đây.

Cuối cùng, stage: production mình chỉ cần copy các files, folders cần thiết xuống là được. Build thử nào:

➜ blog-starter git:(master) ✗ docker build -t blog-with-multistage-dockerfile -f .docker/multistage.dockerfile .

# Check docker images
➜ blog-starter git:(master) ✗ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
blog-with-multistage-dockerfile latest 07c84ea2173a 38 seconds ago 339MB

Tác dụng của node-prune:

 files total 43,924 files removed 12,814 size removed 28 MB duration 866ms

Vậy là cũng giảm được khá khá so với cách build basic.

Cách cuối cùng, mình vẫn sẽ làm giống cách 2 là sử dụng Multi stage nhưng sẽ kết hợp thêm mode: standalone, cách này thì mình cũng mới sử dụng do các anh dev maintain thằng NextJS cũng đang fix bugs cho phần này.

Để sử dụng mode standalone mình sẽ tạo thêm file next.config.js (nếu trong dự án của anh em có rồi thì thôi :v) và thêm 1 đoạn code nhỏ:

module.exports = { output: "standalone"
}

Sau đó, chúng ta tiến hành build thử, ở mode này trong folder .next sẽ tạo ra thêm 1 folder con tên là standalone. Trong đây, NextJS sẽ tự động copy các filespackages cần thiết để chạy, tham khảo thêm

Oke, bắt đầu viết dockerfile:

# Build BASE
FROM node:16-alpine as BASE
LABEL author="ductnn" WORKDIR /app
COPY package.json yarn.lock ./
RUN apk add --no-cache git \ && yarn --frozen-lockfile \ && yarn cache clean # Build Image
FROM ductn4/node:16-alpine AS BUILD
LABEL author="ductnn" WORKDIR /app
COPY --from=BASE /app/node_modules ./node_modules
COPY . .
RUN apk add --no-cache git curl \ && yarn build \ && cd .next/standalone \ # Follow https://github.com/ductnn/Dockerfile/blob/master/nodejs/node/16/alpine/Dockerfile && node-prune # Build production
FROM node:16-alpine AS PRODUCTION
LABEL author="ductnn" WORKDIR /app COPY --from=BUILD /app/yarn.lock ./
COPY --from=BUILD /app/public ./public
COPY --from=BUILD /app/next.config.js ./ # Set mode "standalone" in file "next.config.js"
COPY --from=BUILD /app/.next/standalone ./
COPY --from=BUILD /app/.next/static ./.next/static EXPOSE 3000 CMD ["node", "server.js"]

Vẫn giống cách làm của cách 2, chỉ khác ở stage: production ta chỉ cần copy folders .next/standalone.next/static thay vì copy hết cả folders .nextnode_modules. Oke build thôi ....

➜ blog-starter git:(master) ✗ docker build -t blog-with-multistage-standalone-dockerfile -f .docker/multistage_standalone.dockerfile .

# Check docker images
➜ blog-starter git:(master) ✗ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
blog-with-multistage-standalone-dockerfile latest 07c84ea2173a 38 seconds ago 119MB

WoW image build ra chỉ con 119MB nhẹ đáng kể :v

Kết

Vậy là mình vừa trình bày 2 cách để tối ưu image cho dự án sử dụng NextJS. Anh em cho mình xin ý kiến và nếu có thêm phương pháp tối ưu nào khác thì share cho mình với nhé 🥳 🥳 🥳.

Cảm ơn anh em !!!

Bình luận

Bài viết tương tự

- vừa được xem lúc

Cài đặt WSL / WSL2 trên Windows 10 để code như trên Ubuntu

Sau vài ba năm mình chuyển qua code trên Ubuntu thì thật không thể phủ nhận rằng mình đã yêu em nó. Cá nhân mình sử dụng Ubuntu để code web thì thật là tuyệt vời.

0 0 396

- vừa được xem lúc

Phần 1: Giới thiệu về Kubernetes

Kubernetes là gì. Trang chủ: https://kubernetes.io/. Ai cần Kubernetes.

0 0 100

- vừa được xem lúc

Docker: Chưa biết gì đến biết dùng (Phần 1- Lịch sử)

1. Vì sao nên sử dụng. . .

0 0 104

- vừa được xem lúc

Docker - những kiến thức cơ bản phần 1

Giới thiệu. Nếu bạn đang làm ở một công ty công nghệ thông tin, chắc rằng bạn đã được nghe nói về Docker.

0 0 78

- vừa được xem lúc

Docker: Chưa biết gì đến biết dùng (Phần 2 - Dockerfile)

1. Mở đầu.

0 0 67

- vừa được xem lúc

Docker: Chưa biết gì đến biết dùng (Phần 3: Docker-compose)

1. Mở đầu. . .

0 0 121