Build a NextJS Project is too heavy and some doubts

Hi folks, this is my first post here. If this is not the correct place for this topic, move it please.
I’m doing my first steps in Docker and I would like if possible someone can tell me what I’m doing wrong.

I have a very simple project in NextJS (frontend) only with 3 pages with a Form and graphs and NodeJS (backend) with 4 simple endpoints. I could dockerize and optimize my backend in 200MB but when I’m trying to build my NextJS project docker save -o frontend.tar my-project, is around 1GB or more. I tried a lot of combinations that I read in internet but I didn’t have luck.

And other doubts that I have is:

  1. I’m doing in this way because I want to send my project to another person but I dont’t want to share my .env because there I have my user/password of my MongoDB and Google Token IDs, etc… It is correct, right?
  2. If not, is there another way to don’t share the private information and the user can see the project.

Sorry If my questions are basics. I’m doing my first steps here.

This are my files:

Dockerfile (frontend)

FROM node:18

WORKDIR /app

COPY package*.json ./

COPY . .

RUN npm install

RUN npm run build

EXPOSE 3000

CMD ["npm", "start"]

Dockerfile (backend)

FROM node:alpine AS builder

WORKDIR /app

COPY package*.json ./
COPY . .

RUN npm install && npm cache clean --force

FROM node:alpine

WORKDIR /app

COPY --from=builder /app .

EXPOSE 3001

CMD ["npm", "start"]

docker-compose.yml

version: '3'
services:
  frontend:
    env_file:
      - .env.local
    build:
      context: ./
      dockerfile: ./Dockerfile
    ports:
      - "3000:3000"

  backend:
    env_file:
      - ./api/.env
    build:
      context: ./api
    ports:
      - "3001:3001"

Thanks for your time! :slight_smile:

That doesn’t really make sense in my head:

COPY package*.json ./
COPY . .

Why copy p… and then all? Usually you would only copy p… and src folder. Do you have a Dockerignore file set up? Otherwise you might copy everything into the image (git stuff, logs).

When using two stages, why copy all into second and not only the build dir (assuming something was built)?

Why not use smaller alpine for both?

When running, go into the container (docker exec -it <c-id> sh) and check which files take up space (df …).

1 Like

Thanks @bluepuma77 for your reply and try to help.
Like I said I’m doing my first steps using docker. I’m watching tutorials, chatgpt is helping me too and I’m trying things little by little.

I don’t know what another version can I try to use but for my frontend I need node 18 for nextJS.

And yes in my both folders (frontend and backend) I have .dockerignore file with this files:

.env
node_modules
.next
.vscode
.gitignore
README.md
.dockerignore
.git

Any help is welcome and thanks again for your time! :slight_smile:

Now writting this I realized that in my structure I have my frontend (1) and my backend (2)

And maybe in the process in my frontend is putting my backend.tar inside and also the folder api.
Maybe I need to ignore in .dockerignore to this folder and file or when I do the process of docker save -o, move the tar file to another place before building my docker.

I really doubt that you build your Docker. You build an image. For example when you want to run a virtual machine and you create an image, you don’t build a vmware or virtualbox or hyperv. You build an image. Similarly let’s say you work at Audi, you don’t build a car factory, you build a car in a car factory.

Regarding your question, I think @bluepuma77 asked all the important questions to pioint out what you could do to make your image smaller. Just copy what you really need, nothing more.

There is one thing I would add here

It was a good question, but even if there is a dockerignore, this would make sense when the npm install command is between the two COPY instructions. That way the install command would run only when the package json changes, but not when any of the source files change. You would of course copy the json twice, but that is a small price compared to installing the dependencies every time when you add a new line for example to the source code. The two COPY instruction without building anything between them makes the first COPY line unnecessary.

I didn’t understand your two questions though. It looks like bluepuma didn’t react to that either. It’s unclear what you were referring to. If it meant that you use environment variables so you don’t have to include your passwords in the image, that’s good, but you could also mount a config file so whatever way you choose, the point is that you don’t add anything to the image except that is required for everyone who uses the image and it is not a secret password, token or anything you wouldn’t share with everyone.

1 Like

Thanks for your reply. I did the advices of @bluepuma77 I have this information from my frontend container:

[more files above...]
869.9M  ./node_modules
12.0K   ./src/app/pages/lost-people
16.0K   ./src/app/pages/results
20.0K   ./src/app/pages/salvaments
12.0K   ./src/app/pages/police
12.0K   ./src/app/pages/ambulance
12.0K   ./src/app/pages/assists
88.0K   ./src/app/pages
56.0K   ./src/app/components/graphs
72.0K   ./src/app/components
40.0K   ./src/app/types
12.0K   ./src/app/hooks
16.0K   ./src/app/shared
192.0K  ./src/app/api/auth/signin
8.0K    ./src/app/api/auth/[...nextauth]
204.0K  ./src/app/api/auth
208.0K  ./src/app/api
484.0K  ./src/app
4.0K    ./src/assets
496.0K  ./src
288.0K  ./public
1016.3M 

The most heavy is 870MB only for node_modules and the final size is 1,16GB for all te image.
In my backend the weight is 200MB.

Also I tried only to copy the src folder and when I did I have received this errors like cannot found some paths but this path are OK. Doing COPY . . was working ok, but If I copy only src is not working.

docker-compose

FROM node:18-alpine # I added alpine and the weight doesn't change

WORKDIR /app

COPY package*.json ./
# COPY . .
COPY src ./src # Doesn't work. Look image above.

RUN npm install

RUN npm run build

EXPOSE 3000

CMD ["npm", "start"]

Thanks for your time and try to help me

So basically your image is large because your node_modules folder is large. So unless you can make the application smaller by removing some unnecessary dependencies, you won’t make the image smaller.

That’s the problem. My project is too small.
I have nextjs and some little things more but my package.json is minimal.
My project is very very basic.

Here is my package.json

image

I’m starting to think the problem is dockerize a nextjs project.

Dependencies usually have dependencies themselves.

If I am not mistaken, you should be able to see the full list of dependencies (and transitive dependecies) with npm ls --all

Have you tried it without Docker and you saw a smaller node_modules folder? It is not likely. Although I’m not a nodejs developer, but the number of your dependencies seem to be large enough for that size and that is (as @meyay already wrote) just what you defined. So unless you put additional files into the node_modules folder, that is the real size of it and your only option is to find alternative modules with smaller sizes or drop some dependencies if you can implement the feature easily. Modules sometimes do a lot of things while you may need one of the provided features.

Finally I solved the problem watching a tutorial.
I will leave the solution here for NextJS 14.

Dockerfile

# Source: https://github.com/vercel/next.js/blob/canary/examples/with-docker/README.md

# Install dependencies only when needed
FROM node:18-alpine AS deps
# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
RUN apk add --no-cache libc6-compat
WORKDIR /app
COPY package.json  ./
RUN npm install --frozen-lockfile

# Rebuild the source code only when needed
FROM node:18-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build

# Production image, copy all the files and run next
FROM node:18-alpine AS runner
WORKDIR /app

ENV NODE_ENV production

RUN addgroup -g 1001 -S nodejs
RUN adduser -S nextjs -u 1001

# You only need to copy next.config.js if you are NOT using the default configuration
# COPY --from=builder /app/next.config.js ./
COPY --from=builder /app/public ./public
COPY --from=builder /app/package.json ./package.json

# Automatically leverage output traces to reduce image size 
# https://nextjs.org/docs/advanced-features/output-file-tracing
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static

USER nextjs

EXPOSE 3000

ENV PORT 3000

# Next.js collects completely anonymous telemetry data about general usage.
# Learn more here: https://nextjs.org/telemetry
# Uncomment the following line in case you want to disable telemetry.
# ENV NEXT_TELEMETRY_DISABLED 1

CMD ["node", "server.js"]

package.json

  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start -p ${PORT:=3000}", //This line is the important
    "lint": "next lint"
  },

next.config.js

Add the line:

output: 'standalone'

Doing all the steps I passed from 1.16GB to 158MB.

Thanks to all for trying to help me! :slight_smile:

1 Like