Dockerize a Next.js app
Introduction
Docker is a platform that allows developers to create, deploy, and run applications in a containerized environment. Containerization helps to isolate the application from the host operating system and provides a consistent environment for the application to run in. In this blog post, I will discuss how to use Docker to containerize your Next.js application, show you a simple Dockerfile, show you a multi-stage Dockerfile, and how to use a Dockerfile with docker-compose.
Benefits of using Next.js and Docker
By using Next.js and Docker together, you can take advantage of the benefits of both technologies. Some of these benefits include:
- Scalability:
- Docker makes it easy to scale your application horizontally by spinning up multiple containers.
- Next.js makes it easy to scale your application vertically by optimizing server-side rendering.
- Portability:
- Docker containers can be easily moved between environments, making it easy to deploy your application to different environments.
- Next.js makes it easy to share your application with others by providing a consistent experience across different devices as well as the ability to deploy on a CDN.
- Performance:
- Docker containers provide a consistent environment for your application to run in, which can also improve performance.
- Next.js provides a multitude of options like caching, server side rendering, and incremental static regeneration, which can improve the performance of your application.
Prerequisities
Before we dive into the process of doing that, make sure that you have the following installed on your system:
Creating a Next.js application
If you don't have an existing Next.js application, you can create one by running the following commands in your terminal:
npx create-next-app@latest
On installation you will see the following prompts:
What is your project named?
docker-next-js
Would you like to use TypeScript with this project?
No
Would you like to use ESLint with this project?
Yes
Would you like to use Tailwind CSS with this project?
No
Would you like to use `src/` directory with this project?
Yes
Use App Router (recommended)?
Yes
Would you like to customize the default import alias?
No
This will create a new Next.js application in the docker-next-js
directory. We can navigate into this directory and start the development server by running. Where we can see it running locally on localhost:3000
cd docker-next-js
npm run dev
Dockerizing Next.js
To dockerize a Next.js application, we need to do a couple of things. First we need to create a Dockerfile
in the root directory of our project. The Dockerfile
contains instructions on how to build the Docker image similar to how a software engineer would start and build the app for production.
Simple Dockerfile
To get a simple Dockerfile up and running we can create a Dockerfile
with the following content:
# Base Image
FROM node:14-alpine
# Set working directory
WORKDIR /app
# Install app dependencies
COPY package*.json ./
RUN npm install
# Copy app files
COPY . .
# Build app
RUN npm run build
# Expose port
EXPOSE 3000
# Start app
CMD ["npm", "run", "start"]
Let's go through the Dockerfile
step by step:
FROM node:14-alpine
- This sets the base image to use for the Docker image. In this case, the
node:14-alpine
image is pulled from Dockerhub
- This sets the base image to use for the Docker image. In this case, the
WORKDIR /app
- This sets the working directory to
/app
inside the container.
- This sets the working directory to
COPY package*.json ./
- This copies the local repository's
package.json
andpackage-lock.json
files into the Docker container.
- This copies the local repository's
RUN npm install
- This installs the application dependencies inside the container.
COPY . .
- This copies all the files from the current local directory to the container.
RUN npm run build
- This builds the Next.js application inside the Docker container.
EXPOSE 3000
- This exposes the Docker network's port 3000 within the container.
CMD ["npm", "run", "start"]
- This is the final step that starts the Next.js application from inside the container.
Now that we have our Dockerfile
, we can build the Docker image by running the following command in the terminal:
# This will build the Docker image and tag it with the name `docker-next-js`
docker build -t docker-next-js .
# To run the Docker image we will use
docker run -p 3000:3000 docker-next-js
This will start the container and use Docker's internal network to map the default Next.js port 3000 on the host machine. You should be able to navigate to localhost:3000
or 0.0.0.0:3000
to see your built Next.js app.
While most Software Engineers would be fine that Docker is up-and-running, if we take a look at the size of the Docker image it's a HUMONGOUS 1.17GB BEAST. I've done this when I was early in my career and didn't know any better π¬. Trust me you don't want to be the engineer that pushes up this beast of a container.
So how do we reduce the container size? Simple we use the multi-stage build as recommended by Docker.
Multi-stage Dockerfile
We're going to break this down into two separate chunks (or stages if you like that terminology).The 1st chunk is the installation and build portion, the 2nd chunk is just copying over the necessary built Next.js files to serve (everything in the ./public
and ./standalone
directories)
# Chunk 1
#
# Step 1. Use node-18-alpine as the base image
FROM node:18-alpine AS base
# Step 2. Rebuild the source code only when needed
FROM base AS builder
# Step 3. Set working directory to `/app`
WORKDIR /app
# Step 4. Copy over the `package.json` and any lock files for a users' preferred package manager
COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* ./
# Step 5. Install dependencies based on the preferred package manager
RUN \
if [ -f yarn.lock ]; then yarn --frozen-lockfile; \
elif [ -f package-lock.json ]; then npm ci; \
elif [ -f pnpm-lock.yaml ]; then yarn global add pnpm && pnpm i; \
# Allow install without lockfile, so example works even without Node.js installed locally
else echo "Warning: Lockfile not found. It is recommended to commit lockfiles to version control." && yarn install; \
fi
# Step 6. Copy all the files from the local repo into the Docker container
COPY . .
# Step 7. Optional, disables Nextjs telemetry
ENV NEXT_TELEMETRY_DISABLED 1
# Step 8. Build Next.js based on the preferred package manager
RUN \
if [ -f yarn.lock ]; then yarn build; \
elif [ -f package-lock.json ]; then npm run build; \
elif [ -f pnpm-lock.yaml ]; then pnpm build; \
else yarn build; \
fi
# End of Chunk 1
# Chunk 2
# Step 1. Get the production image we just built in the Chunk 1
FROM base AS runner
# Step 2. Set the working directory to `/app`
WORKDIR /app
# Step 3. We don't want to run production as the `root` user
# so we create a `nextjs` user and add it to the `nodejs` group.
# This is recommended to prevent unauthorized use if your app is compromised
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
# Step 4. We change from `root` to `nextjs` user
USER nextjs
# Step 5. Copy all the necessary files generated by Next.js
COPY --from=builder /app/public ./public
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
# Step 6. Optional, disable nextjs telemetry
ENV NEXT_TELEMETRY_DISABLED 1
# Step 7. Optional but recommended to set the production environment
# PORT and HOSTNAME environment variables
ENV PORT 3001
ENV HOSTNAME localhost
# Step 8. We run the `server.js` that is generated from `./next/standalone/server.js`
CMD ["node", "server.js"]
Let's go ahead and build our multi-stage Dockerfile
to compare against our original.
# This will build the Docker image and tag it with the name `docker-next-js`
docker build -t docker-next-js-2 .
So while our individual Dockerfile
looks like a lot more lines, what we have done is actually trimmed unnecessary files. By doing so our 1.17 GB becomes a mere 210 MB which is a decrease of almost 82% of the container π(and yes, your DevOps Engineers will thank you)
Setting up our docker-compose.yml
So now that we have a smaller container let's setup a docker-compose.yml
file which we can use to define and run our containers. This also helps to not have to remember all the nuances and commands of the docker
CLI.
First let's create a docker-compose.yml
in the root directory right next to our Dockerfile
version: "3"
services:
next-app:
container_name: docker-next-js
build:
context: .
dockerfile: Dockerfile
restart: always
ports:
- 3001:3001
This configuration defines a single service called next-app
. The build
property specifies the context and location of the Dockerfile
, while the ports
property marries the Docker network ports to the ports on the local machine.
Now all we have to run to build our application and start it is:
docker compose up --build
Conclusion
In this blog post, we discussed how to use Docker to dockerize a Next.js application. Dockerizing applications provides many benefits, including portability and consistency across different environments. By using Next.js and Docker together, you can take advantage of the benefits of both of these awesome technologies.
With the Dockerfile
and commands provided, you can easily dockerize your own Next.js applications. By using Docker, you can easily share your application with others, without needing to worry about the environment in which the application will be run. This makes it easier to collaborate with other developers, as well as deploy your application to different environments.
Overall, Dockerizing a Next.js application is a straightforward process that can provide many benefits. As more and more developers adopt Docker as a way to deploy their applications, it's important to stay up-to-date with the latest best practices and trends in the industry.
Good luck Dockerizing your Next.js application!
Here are some other resources for further reading:
- 10 Best Practices for Dockerizing Node.js Web Applications
- Serverless Next.js with Docker and AWS Fargate
If you enjoyed this article please feel free to connect with me on Dev.to or on LinkedIn