Dockerizing a NextJS app
This is the first article of a three parts blog post. We will be deploying a custom NextJS app to AWS ECS with Fargate.
TL;DR: You can get all the script/stack in my GitHub repos. Add a star if it helped you, I will keep it updated if other are using it !
The stack
The goal is to deploy a full NextJS stack with NGINX in front on AWS Elastic Container Service (ECS).
We are going to dockerize the app part (NextJS + NGINX), then we will deploy the images on ECS with Fargate in front of it, finally we will set up the CI.
Setting up the project
It will be quite minimale. We are going to start from a blank slate.
npx create-next-app@latest
Our final project structure will be the following :
pages/
public/ <- NextJS files
...
Dockerfile <- NextJS Dockerfile
nginx/ <- Nginx conf folder
Dockerfile <- Nginx Dockerfile
default.conf <- Nginx configuration
Dockerizing NextJS
Now let’s create the Dockerfile copied from the official repos. I made some tiny updates, feel free to start from NextJS !
# Install dependencies only when needed
FROM node:16-alpine AS deps
# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3
# to understand why libc6-compat might be needed.
RUN apk add --no-cache libc6-compat
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
# Rebuild the source code only when needed
FROM node:16-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:16-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"]
Before building this let’s get through this DockerFile ! There are 3 main stages :
- deps
- builder
- runner
Stages allow you to keep your image as tiny and optimised as possible. You can read more here
FROM node:16-alpine AS dips
The docker env will be an alpine linux with node 16 installed.
RUN npm ci
The ci command is used in order install the deps, it allows us to assure that it will be a clean install.
COPY --from=deps /app/node_modules ./node_modules
If by now you are not familiar with multistage build (go read the documentation I linked above) this command copies the node module installed in the deps stage.
RUN addgroup -g 1001 -S nodejs
RUN adduser -S nextjs -u 1001
To increase the security we are creating a nodejs group with a nextjs user.
USER nextjs
Our container will run as the nextjs user.
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
The file tracing helps reduce the size of deployments and will create a minimal server.js
file.
Warning: You must add outputStandalone: true
to your next.config.js
.
EXPOSE 3000
ENV PORT 3000
CMD ["npm", "start"]
It exposes the 3000 port and run the NextJS app on this port.
Now that we know what we are doing, we can build the container and run it !
docker build -t nextjs-docker .
docker run -p 3000:3000 nextjs-docker
And voila ! We can access our NextJS’s app at localhost:3000
Dockerizing Nginx
We will create a nginx folder that will contain our nginx Dockerfile and the main configuration.
nginx/
Dockerfile
default.conf
Now we can add a minimal nginx configuration.
upstream nextjs-app {
server nextjs-docker:3000;
}
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name _;
location / {
proxy_pass http://nextjs-app;
}
}
Let’s create the Dockerile, here are some great ressources in order to Dockerize our nginx !
FROM nginx:alpine
# Remove the default conf
RUN rm /etc/nginx/conf.d/default.conf
# Copy our custom nextjs nginx conf
COPY ./*.conf /etc/nginx/conf.d/
EXPOSE 80
CMD [ "nginx", "-g", "daemon off;" ]
Pretty much it will delete the default configuration, paste all .conf files from our nginx folder and will expose and run Nginx on port 80.
We can now build the Nginx docker image :
docker build -t nginx-nextjs .
Finally we have to create a docker-compose.yml in order to run it all !
At the root folder create the docker-compose.yml file.
version: '3.9'
services:
nextjs-docker:
build: ./
nginx-nextjs:
build: ./nginx-nextjs
ports:
- 80:80
Bind the nginx 80 port in order to encapsulate the nextjs app.
Now we can finally run our app !
Note: If you did not build the image before you can do it docker-compose up –build
docker compose up
That’s it ! You can now access your NextJS app at http://localhost:80
In the next post we will deploy this app on ECS with Fargate.