node.js - Menggunakan Docker untuk Nextjs dengan TypeScript


Saya sedang mencoba membangun Docker Image dari aplikasi frontend Nextjs (React) saya untuk produksi dan saat ini terjebak pada integrasi TypeScript.

Berikut adalah Dockerfile.

FROM node:14-alpine3.14 as deps

RUN apk add --no-cache tini
ENTRYPOINT ["/sbin/tini", "--"]
EXPOSE 4500
RUN apk add --no-cache libc6-compat

RUN mkdir /app && chown -R node:node /app
WORKDIR /app
COPY --chown=node:node package.json package-lock.json ./
RUN npm ci --production && npm cache clean --force

FROM node:14-alpine3.14 as build
RUN mkdir /app && chown -R node:node /app
WORKDIR /app
ENV NODE_ENV=production
COPY --chown=node:node . ./
COPY --from=deps /app/node_modules ./node_modules
RUN npm run build


FROM node:14-alpine3.14 as prod
RUN mkdir /app && chown -R node:node /app
WORKDIR /app
ENV NODE_ENV=production
ENV PORT=7777
COPY --from=build /app ./
USER node
CMD ["node_modules/.bin/next", "start"]

Sekarang ini menghasilkan sebuah kesalahan:

It looks like you're trying to use TypeScript but do not have the required package(s) installed.

Pada dasarnya karena saya menjalankan npm ci --production, itu tidak menginstal devDependencies di mana typescript berada.

Setelah mencari, saya telah menemukan beberapa solusi.

Solusi 1: Yang pertama adalah menambahkan typescript ke dependencies. Meskipun disarankan bahwa karena typescript hanya devDependency, itu seharusnya tidak ada dalam dependencies normal.

Solusi 2: Menambahkan typescript melalui npm install. Pada dasarnya sama seperti solusi 1. Saya mengubah Dockerfile menjadi:

FROM node:14-alpine3.14 as deps

COPY --chown=node:node package.json package-lock.json ./
RUN npm ci --production && npm cache clean --force

Added typescript and node types here
RUN npm install --save-dev typescript @types/node

Dalam kasus ini, ukuran total gambar menjadi: 981,58 MB.

Solusi 3: Melakukan npm install sederhana alih-alih npm ci --production.

FROM node:14-alpine3.14 as deps

COPY --chown=node:node package.json package-lock.json ./

Simple npm install
RUN npm install && npm cache clean --force

Dalam kasus ini saya akhirnya juga menginstal semua devDependencies. Dalam kasus ini ukuran total gambar adalah: 537,32 MB.

Sekarang saya punya beberapa pertanyaan mengenai ini.

Pertanyaan 1: Mengapa menambahkan typescript melalui npm install --save-dev typescript @types/node dalam Solusi 2 mengakibatkan ukuran file lebih besar dibandingkan Solusi 3 di mana kita menginstal semua dependensi?

Pertanyaan 2: Jika di Solusi 3 saya melakukan npm ci alih-alih npm install, total ukuran gambar menjadi 972,59 MB. Mengapa menggunakan npm ci meningkatkan ukuran gambar? Bukankah seharusnya hanya menginstal paket yang tepat berdasarkan package-lock.json.

Pertanyaan 3: Saya melihat diskusi Diminta untuk menginstal Typescript padahal sudah terinstal saat membangun gambar Docker.

Itu menyarankan solusi dengan build bertahap seperti ini.

FROM gcr.io/companyX/companyX-node-base:12-alpine AS build

Copy in only the parts needed to install dependencies
(This avoids rebuilds if the package.json hasn’t changed)
COPY package.json package.lock .

Install dependencies (including dev dependencies)
RUN npm install

Copy in the rest of the project
(include node_modules in a .dockerignore file)
COPY . .

Build the project
RUN npm run build

Second stage: runtime
FROM gcr.io/companyX/companyX-node-base:12-alpine

ENV NODE_ENV=production

Again get dependencies, but this time only install
runtime dependencies
COPY package.json package.lock .
RUN npm install

Get the built application from the first stage
COPY --from=build /app/dist dist

Set runtime metadata
EXPOSE 3000
CMD [ "npm", "start" ]
CMD ["node", "dist/index.js"]

Bukankah solusi ini buruk karena Anda akhirnya menginstal dependensi dua kali dalam kasus ini. Sekali di tahap build dan kedua di tahap runner meskipun Anda hanya menginstal dependensi produksi di tahap runner.

Saya mencoba solusi ini dan seperti yang diharapkan, saya akhirnya mendapatkan ukuran gambar sebesar 1,18 GB.

Pertanyaan 4: Solusi mana dari yang di atas yang lebih baik untuk dipilih? Atau apakah ada cara yang lebih baik untuk melakukan ini?

Jawaban #1

Gunakan wadah perantara untuk menginstal hanya paket untuk produksi

FROM node:14-alpine AS build

Disable telemetry
ENV NEXT_TELEMETRY_DISABLED 1

WORKDIR /build

Copy package and package-lock 
COPY package.json package-lock.json ./

Clean install dependencies based package-lock
Note: We also install dev deps as typeScript may be needed
RUN npm ci

Copy files
Use .dockerignore to avoid copying node_modules and others folders and files
COPY . .

Build application
RUN npm run build

=======================================
Image generate dependencies production
=======================================
FROM node:14-alpine AS dependencies

Environment Production
ENV NODE_ENV production

WORKDIR /dependencies

Copy package and package-lock 
COPY --from=build /build/package.json .
COPY --from=build /build/package-lock.json ./

Clean install dependencies based package-lock
RUN npm ci --production

=======================================
Image distroless final
=======================================
FROM gcr.io/distroless/nodejs:14

Mark as prod, disable telemetry, set port
ENV NODE_ENV production
ENV PORT 3000
ENV NEXT_TELEMETRY_DISABLED 1

WORKDIR /app

Copy from build
COPY --from=build /build/next.config.js .
COPY --from=build /build/public/ ./public
COPY --from=build /build/.next ./.next
COPY --from=dependencies /dependencies/node_modules ./node_modules

EXPOSE 3000

Run app command
CMD ["node_modules/.bin/next", "start"]

Jawaban #2

Untuk kasus ini, Anda dapat menggunakan gambar dasar https://github.com/ryanbekhen/feserve/pkgs/container/feserve sebagai tahap produksi. Ini adalah gambar yang saya buat berdasarkan keluhan yang terjadi pada frontend. Gambar dasar ini hanya sekitar 8 MB, jadi tidak memakan banyak penyimpanan.

Artikel terkait