As we all know Docker is not just another buzzword, but one of the best containerization tool for software engineers. With the ability to ship any application regardless of its environmental requirements docker has solved problems in development and production stage like; inconsistencies when running your application with the phrase popularly known as "urghh..but it worked on my PC!"
This tutorial shows how to Dockerize a Nestjs app, built with the Nestjs CLI and Docker.
Requirements
- Docker installed
- A NestJs app
1. Docker Installation
a) For the installion guide on windows visit https://docs.docker.com/docker-for-windows/install/
b) For the installation guide on Linux visit
https://docs.docker.com/install/linux/docker-ce/ubuntu/
c) For the installation guide on macOs visit https://docs.docker.com/docker-for-mac/
2. NestJs Application
Create and develop your NestJs application or use the sample project provided below for the demo:
https://github.com/abbasogaji/dockerized-nestjs-production-app
Your nestJs application project structure usually would look like this:
And its associated npm commands for running, testing and building your nestJs project are found in your package.json's scripts object (property);
Some of these commands will be used when we dockerize our application.
NOW WE WRITE OUR Dockerfile
FROM node:10 AS builder
WORKDIR /app
COPY ./package.json ./
RUN npm install
COPY . .
RUN npm run build
FROM node:10-alpine
WORKDIR /app
COPY --from=builder /app ./
CMD ["npm", "run", "start:prod"]
Breakdown
-
The build process is divided into two steps (multi-step build);
- The First step uses node:10 as base image, installs dependencies and transpiles Typescript files to Javascript. Full node image is used for this process since it contains all the necessarity build tools required for dependencies with native build (node-gyp, python, gcc, g++, make)
- The Second step uses node:10-alpine (lightweight version), copies file-system from the first step's (intermediate) container, sets command for running the application. A multi step build process was setup to efficiently install our dependencies at first step and to run a lightweight container from image the of our final step.
-
In First step;
- We will set our application directory to "/app", so our application is bundled into "/app" in our docker image file-system
- We will COPY our "package.json" then run "npm install" before we copy the remaining project files because that will prevent unnecessary installs anytime we re-build our image and make use of cached installs.
- We will run "npm run build" to generate production files at "dist/main" directory which is required by our "run" command in production context i.e (npm run start:prod)
-
In Final step;
- We will set our application directory to "/app", so our application is bundled into "/app" in our docker image file-system
- We will copy the file-system of the previous step.
- We will set command for running our application
- Create .dockerignore to avoid copying node_modules; so in your .dockerignore you type in "node_modules" and now you have your Dockerfile and .dockerignore file at your base directory of your project as shown below;
- Now we build our image, assign a tag with the format "docker-username/project-name" then push to docker hub by running:
docker build -t exampleuser/dockerized-nest-project .
docker push exampleuser/dockerized-nest-project
Question: Now we are done; but wait a minute? EXPOSE port command was omitted?.
Answer: because EXPOSE command is not respected by some Paas (Platform as service) providers e.g (Heroku).
but if you deploy your docker image to a cloud provider that requires EXPOSE command which maps your docker network ports to your host port. then you should add "EXPOSE 3000" IN Dockerfile before the last CMD command;
# Using Node:10 Image Since it contains all
# the necessary build tools required for dependencies with native build (node-gyp, python, gcc, g++, make)
# First Stage : to install and build dependences
FROM node:10 AS builder
WORKDIR /app
COPY ./package.json ./
RUN npm install
COPY . .
RUN npm run build
# Second Stage : Setup command to run your app using lightweight node image
FROM node:10-alpine
WORKDIR /app
COPY --from=builder /app ./
EXPOSE 3000
CMD ["npm", "run", "start:prod"]
Note : Using Multi-Step build process to dockerize our nestjs application isn't a necessity and was only used because we would like our image to be lightweight. for a single step build process we will write the following;
# Using Node:10 Image Since it contains all
# the necessary build tools required for dependencies with native build (node-gyp, python, gcc, g++, make)
FROM node:10
WORKDIR /app
COPY ./package.json ./
RUN npm install
COPY . .
RUN npm run build
# EXPOSE 3000
CMD ["npm", "run", "start:prod"]
Then finally we will have to rebuild, deply and run the image
Top comments (19)
I don't know why this is? , I followed the instructions on the tutorial, but I tried it, and it still didn't solve it!
You might be missing some build dependencies using the alpine version, you can use this instead;
Sorry, I did not post the complete configuration. I tried it and found that it was a problem with WORKDIR. It was normal when I ran it for the first time. This problem occurred the second time docker-compose up. is normal. I am trying to find out why.
" Error: Cannot find module '/app/dist/main.js" it was looking main.js at "/app/dist/main.js" instead of "/app/bd-url-query/dist/main.js"
Just take a look at what you have in your directory, add the ls command i.e "RUN ls -l" before "CMD....." line in your Dockerfile and inspect the files you have there
Nice post!
Just a small comment - I'm fairly certain that it's not necessary to do it in two steps by using first the full node image and then node-alpine. I got it working with just 1 step using node-alpine for everything:
FROM node:10-alpine
WORKDIR /app
COPY ${PWD}/package.json ./
RUN yarn
COPY . .
RUN yarn build
EXPOSE 5000
CMD ["sh", "-c", "yarn typeorm migration:run && yarn start:prod"]
Very true, although they might be scenarios where your dependencies rely on native builds, and that might require you to install build tools like node-gpy (which is written in python [meaning also need to install python] ), make, gcc, g++ which might not exists on alpine version,
Although you can still get away with it by installing build tools via apk in Dockerfile
This man...thanks for the article... this is Goodluck
Mahnn how u doing๐
Very good man and you?
Thanks for the post! it was a lot of help.
thanks man
Well explained, nice article.
Thanks a lot for sharing!!! :)
Good post. Thanks
Brilliant! Thank you!!
Hi Buddy.
This post is really good. It fixed my issue.
Thanks mate.