How to deploy your Nextjs app with Docker

nextjs docker

Learn how to Dockerize your Next.js app and create a nextjs docker file for seamless deployment. Our step-by-step guide will help you get started with nextjs docker image for production and ensure your Next.js app is running smoothly in no time.


If you're looking to deploy Next.js, it's possible to use any hosting provider that supports Docker containers. The dockerized next js app can run on any cloud provider or using container orchestrators like Kubernetes or AWS ECS. This blog is about how to create a production ready docker image file for nextjs web app and deploy the ssr web app with docker.

MultiStage Docker Image file for NextJs

The blog is written based on the following versions,

  • Next: 13.3
  • Docker: 20.10.8
  • Node: 18

To containerize the Next.js application, we will employ a multistage Dockerfile that adheres to best practices and facilitates the reduction of the final Docker image size of the Next.js app.

NextJs Output File Tracing to reduce deployment size for docker

The Next.js framework has the capability to generate a self-contained directory that exclusively duplicates the essential files required for a production deployment, including specific files located in the node_modules folder.

To enable this set the output attribute in next.config.js as standalone

module.exports = {
  output: 'standalone'
}

This will create a folder at .next/standalone which can then be deployed on its own without installing node_modules. Moreover, a concise server.js file is generated as well, which can substitute the "next start" command. By default, this streamlined server omits the public and .next/static folders because it's preferable to delegate their management to a CDN. However, if necessary, the folders can be manually duplicated into standalone/public and standalone/.next/static directories, after which the server.js file will serve them automatically.

Docker file for NextJs: Build Stage

We'll be utilizing node 18 alpine linux as the underlying image for the Dockerfile. Now, let's inspect each line of the subsequent Dockerfile for this stage and comprehend the diverse steps involved.

FROM node:18-alpine AS build
# Install dependencies only when needed
# 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 and install the dependencies for the project
COPY package.json package-lock.json ./
RUN npm ci
# Copy all other project files to working directory
COPY . .
# Run the next build process and generate the artifacts
RUN npm run build

Initially, we establish the Alpine Node 18 Docker as the base image with the stage name "build". Next, we update all the packages in this Docker image to their most recent versions with the second line, and then designate the working directory of this image as /app using the third line. Subsequently, the package manager files are replicated to the working directory, after which the npm ci command is run to install all the dependencies. Then the all other project files are copied and we execute build process which culminates in the creation of a .standalone folder that contains all the generated artifacts.

MultiStage Docker file for NextJs: Runner Stage

# we are using multi stage build process to keep the image size as small as possible
FROM node:18-alpine
# update and install latest dependencies, add dumb-init package
# add a non root user
RUN apk update && apk upgrade && apk add dumb-init && adduser -D nextuser 


# set work dir as app
WORKDIR /app
# copy the public folder from the project as this is not included in the build process
COPY --from=build --chown=nextuser:nextuser /app/public ./public
# copy the standalone folder inside the .next folder generated from the build process 
COPY --from=build --chown=nextuser:nextuser /app/.next/standalone ./
# copy the static folder inside the .next folder generated from the build process 
COPY --from=build --chown=nextuser:nextuser /app/.next/static ./.next/static
# set non root user
USER nextuser
# expose 3000 on container
EXPOSE 3000

# set app host ,port and node env 
ENV HOST=0.0.0.0 PORT=3000 NODE_ENV=production
# start the app with dumb init to spawn the Node.js runtime process
# with signal support
CMD ["dumb-init","node","server.js"]

As this is a multi-stage Docker build for Next.js, we will transfer the generated artifacts from the first stage to the second stage. This stage entails obtaining a fresh Alpine base image for Docker and updating all of its packages. Afterward, the working directory is defined as /app, followed by the replication of the public folder from the build stage to the present Docker image working directory (i.e., /app). This step ensures that only the public files are copied since they are not included in the build process. Next, the files located in the standalone folders are copied to the working directory. Lastly, we must copy all the static files found in the static folder to /.next/static/ inside the working directory. By following these steps, we can minimize the overall Docker image size of the final Next.js image.

Subsequently, a non-root user called nextuser is established, and port 3000 is exposed. Following this, the host, port, and node_env are defined, and the app is launched via the Node process. The last phase in this Dockerfile involves initiating the Node server with the server.js file serving as the entry point. Additionally, we are utilizing an init system named dumb-init to optimize the handling of the Node process. Further details about dumb-init can be found here.

Docker file for Production Ready Nextjs Project

FROM node:18-alpine AS build
# Install dependencies only when needed
# 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 and install the dependencies for the project
COPY package.json package-lock.json ./
RUN npm ci
# Copy all other project files to working directory
COPY . .
# Run the next build process and generate the artifacts
RUN npm run build

# we are using multi stage build process to keep the image size as small as possible
FROM node:18-alpine
# update and install latest dependencies, add dumb-init package
# add a non root user
RUN apk update && apk upgrade && apk add dumb-init && adduser -D nextuser 

# set work dir as app
WORKDIR /app
# copy the public folder from the project as this is not included in the build process
COPY --from=build --chown=nextuser:nextuser /app/public ./public
# copy the standalone folder inside the .next folder generated from the build process 
COPY --from=build --chown=nextuser:nextuser /app/.next/standalone ./
# copy the static folder inside the .next folder generated from the build process 
COPY --from=build --chown=nextuser:nextuser /app/.next/static ./.next/static
# set non root user
USER nextuser

# expose 3000 on container
EXPOSE 3000

# set app host ,port and node env 
ENV HOST=0.0.0.0 PORT=3000 NODE_ENV=production
# start the app with dumb init to spawn the Node.js runtime process
# with signal support
CMD ["dumb-init","node","server.js"]

Nextjs Docker Deployment and docker compose

Once the docker image of next app is built, this image can be pushed to docker hub or artifactory or other docker image repository used by the teams. This image can then be run on the container orchestration stacks like ECS or K8.

Nextjs Docker Sample Repo

You can check the sample project for docker file for nextjs.

Furthermore, you can explore how to host nextjs ssr website using aws lambda and cloudfront in this blog post.