Dockerizing NEXT JS V14 + Application using output Standalone and Self Hosting.
Let’s jump right into it. I’ll start with the prerequisites.
- Docker installed on your server or your local machine.
- Knowledge of nextjs standalone export https://nextjs.org/docs/app/api-reference/next-config-js/output.
- Knowledge of command line usage.
- Make sure you have the output set to standalone in your next.config.js.
- Let’s get started.
Add a standalone command in your package.json
"scripts": {
"dev": "next dev",
"build": "next build",
"standalone": "next build && cp -r public/ .next/standalone && cp -r .next/static .next/standalone/.next",
"start": "next start",
"lint": "next lint"
},
The added script has been modified to allow the copying of the assets if we are not using a CDN. Just copy the script as it is. It’ll work.
Dockerignore file
Ignores files you don’t want them to end up in your container.
node_modules/
npm-debug.log
.env
.DS_Store
.vscode/
.yarn
Dockerfile.
# Specify the base image
FROM node:20-alpine
WORKDIR /app # create our working directory which is the root of our project
# at a directory called app
COPY package*.json ./ #first copy package.json and package lock that
#subsequent builds will be fast (results here will be cached)
RUN npm install -g yarn --force #Istalling yarn since I'm using yarn
RUN yarn #installing dependancies
COPY . .
RUN yarn standalone # building using standalone
# check on the attached command on the build standalone
#that I have modified to copy static assets e.t.c
#Runtime stage installes nodejs to make the container smaller
FROM alpine:3.20
RUN apk update && apk add --no-cache nodejs
RUN addgroup -S node && adduser -S node -G node
USER node
RUN mkdir /home/node/code && chown -R node:node /home/node/code
WORKDIR /home/node/code
COPY --from=0 /app/.next/standalone .
EXPOSE 3000
CMD [ "node", "server.js" ]
Explanation.
- The first line FROM node:20-alpine specifies the image we will be using; in this case, I have chosen node alpine since it is minimalistic.
- The second line, WORKDIR /app, sets up the working directory to be in the container's root at a directory called app, and now subsequent commands will be run from this directory.
- Also, note this is a multi-stage container build whereby we do the building and finally copy the required stuff in the final container since, like, for instance, we don’t need yarn in the production build, e.t.c
- The following line, COPY package*.json ./, copies the package.json and package.lock.json to the ./, which is, in this case, our working directory. We do this because we don’t have to rebuild our dependencies in subsequent builds. If, in any case, they haven’t changed, these will be cached.
- The following line, RUN npm install -g yarn — force installs yarn since that is what I’m using for my project. If you are using a different tool, make sure to install it here, e.g. pnpm
- The following line, RUN yarn, installs the dependencies. If you are using npm, you run the npm command here, and there is no need to install anything like in the previous step. If using pnpm, you use pnpm to install the dependencies.
- The following line is COPY . ., copies the folders in our directory to the container’s work directory, which is /app.
- The following line, RUN yarn standalone, runs the script we added in the package.json, builds the application, does the required stuff, and bundles everything for us. At this point, our application is ready to run using a node.
- We now initialize another stage. FROM alpine:3.20 , This command installs a minimalistic alpine Linux version, which does not even have a node installed. We don’t use node-alpine because it comes with npm, which we don’t need to run our application.🥳🥳
- The next line RUN apk update && apk add — no-cache nodejs , this updates the container to use the latest dependencies and installs nodejs.
- RUN addgroup -S node && adduser -S node -G node, this command creates a group called node adds user node to the group node.
- USER node, command selects user node as the default, similar to switching to that user in linux.😉 This is for security purposes.
- RUN mkdir /home/node/code && chown -R node:node /home/node/code creates a directory in the user (‘node’) home directory called code and makes sure the directory is owned by the user node.
- WORKDIR /home/node/code, makes the directory we created as the defacto.
- COPY — from=0 /app/.next/standalone . This command copies what was built in stage 0, i.e., the first stage. NB: if you don’t name the stages, they are named starting from 0. Remember we had the /app directory in the root of the first container, so whatever is there, we are now copying that to our WORKDIR, which is /home/node/code 😉😉
- EXPOSE 3000. This is just like a comment to tell other developers which port our application is running on✅✅
- CMD [ “node”, “server.js” ]. This is the command that is run inside our container😉
Docker Compose File
You don’t need to use docker-compose; you are good to go with the above container. But by using docker-compose, we get some advantages, and it’s simpler to scale our applications and attach other services, bring them down and up as we want.
version: "3.8"
services:
web:
build: .
ports:
- 3034:3000
env_file:
- .env
restart: always
Explanation
- version: “3.8”. Specifies the docker-compose version we are running.
- Services. Starts a new context where we can add our services.
- web: Names our first service; you can add any name here. You can even call it cow or goat or something. It doesn’t matter here. You just add a name and be descriptive so you can later know what you were doing.
- build: . This specifies the build context where to find our dockerfile
- ports: Specifies a new context inside our web context where we can add the ports to expose in the format outside:inside. Outside is the port to expose to the outside world, and Inside is the port inside the container where your application is running
- - 3034:3000. Specifies the ports in the format outside:inside.
- env_file: specifies a new context inside web where you can add your environment variables in a file. More directives are found on the docker website https://docs.docker.com/
- - .env. Specifies the file. NB the file must exist
- restart: always. Specifies that our container should restart in case the server is restarted
Run the Application
To run the application, just use the following command.
$ docker-compose up -d # the -d specifies to run on the background
$ docker-compose up -d --build # the --build specifies the container to rebuild.
Conclusion
That is it from me. Feel free to follow me, or let’s chat here
https://github.com/techwithtwin
https://techwithtwin.com.
Happy coding.