Stefan Natter
natterstefan

natterstefan

How to Dockerize a NextJS application

How to Dockerize a NextJS application

Let's run NextJS in a Docker Image.

Stefan Natter's photo
Stefan Natter

Published on Feb 27, 2021

7 min read

Listen to this article

Next.js lets you build production-grade applications that scale with ease. You can build static and dynamic websites with world-class development experience. The team behind Guillermo Rauch has built an environment that is preferred by thousands of software engineering teams every day.

So did we for our company projects. At my company marqant digital, we have decided to not build a custom-built application setup (with SSR, etc.) again, but to use an already existing solution. We were faced with a decision between Gatsby and Next.js. Although I think Gatsby is also great software, the requirements of the project favored Vercel's product.

And as with all other (micro-)services we build and use around our products, our React and Node applications need to run in a Docker environment. To expand my digital garden, and at the same time provide you with added value, I share with you our latest Dockerfile we use in our Next.js projects.

I assume you have Docker Desktop installed, have worked with Docker, and have a knowledge of the basics of Next.js.

Multi-Stage Dockerfile for Next.js Applications

Let's take a look at our current Dockerfile. You might know some of the things I do here already, but let's take a look at each one of them in detail. If you build a static site with Next.js you might not need all the stages and layers we use in our Dockerfile. But our app requires us to use Next.js this way. So keep that in mind, when using the solution below, please. Here's the complete Dockerfile.

ARG node_version=14.15.4
ARG node_image=node:${node_version}-alpine

# STAGE 1
FROM $node_image as builder

ENV NEXT_TELEMETRY_DISABLED=1

WORKDIR /app/

COPY package.json yarn.lock ./
RUN yarn install --frozen-lockfile --no-progress

COPY next.config.js ./
COPY components ./components/
COPY pages ./pages/
COPY public ./public/
COPY styles ./styles/

RUN yarn build

# STAGE 2
FROM $node_image as production

WORKDIR /app/

COPY --from=builder /app/package.json /app/yarn.lock ./
RUN yarn install --frozen-lockfile --production=true --no-progress

# STAGE 3
FROM $node_image

ENV NEXT_TELEMETRY_DISABLED=1
ENV NODE_ENV=production

WORKDIR /app/

COPY --from=production /app/node_modules ./node_modules
COPY --from=builder /app/.next ./.next
COPY --from=builder /app/next.config.js ./
COPY --from=builder /app/public ./public
COPY --from=builder /app/package.json ./

EXPOSE 3000
CMD npm run start

What you see here is a multi-stage Dockerfile. By using multiple stages you can significantly reduce the size of a Docker image. This is due to the fact that you only copy what you need from the previous stage to the next one.

Let's take a look at each stage individually. Let's start with Stage 1.

# Because we use more than one stage, we use ARG
# to determine some global variables in our
# Dockerfile. In this case, our node_version and the
# selected docker base image.
ARG node_version=14.15.4
ARG node_image=node:${node_version}-alpine

# STAGE 1 is called "builder"
# We can reference this name later
FROM $node_image as builder

# disable Next.js' analytics (https://nextjs.org/telemetry)
ENV NEXT_TELEMETRY_DISABLED=1

# everything we do will happen in this folder in the image
WORKDIR /app/

# configs and dependency handling
COPY package.json yarn.lock ./

# install all dependencies
RUN yarn install --frozen-lockfile --no-progress

# copy source code of our app
COPY next.config.js ./
COPY components ./components/
COPY pages ./pages/
COPY public ./public/
COPY styles ./styles/

# build the app
RUN yarn build

Now we install only the production dependencies in Stage 2 as a preparation for the last and final Stage 3.

# STAGE 2
FROM $node_image as production

WORKDIR /app/

COPY --from=builder /app/package.json /app/yarn.lock ./
RUN yarn install --frozen-lockfile --production=true --no-progress

In Stage 3 we copy the results from Stage 1 (the finished application) and Stage 2 (the production node dependencies) together and start the app. This is what it looks like.

# STAGE 3
FROM $node_image

ENV NEXT_TELEMETRY_DISABLED=1
ENV NODE_ENV=production

WORKDIR /app/

# copy the production node_modules from STAGE 2
COPY --from=production /app/node_modules ./node_modules

# copy the result from the builder (STAGE 1)
# where we built the app into STAGE 3
COPY --from=builder /app/.next ./.next
COPY --from=builder /app/next.config.js ./
COPY --from=builder /app/public ./public
COPY --from=builder /app/package.json ./

EXPOSE 3000
CMD npm run start

That is our Dockerfile. If you now run this image you have a production-ready Next.js application.

The easiest way to start the image is with the help of docker-compose and this docker-compose.yml file. Create the file and simply run docker-compose up. After the image is built, you should see some logs indicating that the app has started. You can now open localhost:3000 again in your browser and you are done.

# docker-compose.yml
version: "3.8"

services:
  app:
    build:
      context: .
    ports:
      - "3000:3000"

PS: Take a look at the official Next.js docs for further details about how to dockerize Next.js.


Share 💛

Do you like this article? Do you want to support me? Tell your friends on Twitter about it. Thank you!

Questions and Feedback

You got any questions or want to add your thoughts? Let me know in the comments below, please.

Let's connect

 
Share this