How to Dockerize your NestJS App for production
Tags: docker, nestjs, npm, devops
Published: 2019-12-28 13:41:00

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
1. Docker installed
2. 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:

![Nestjs project structure](https://user-images.githubusercontent.com/34817010/71542915-ae8e7580-296c-11ea-87d9-e22298afbf0e.jpg)

And its associated npm commands for running, testing and building your nestJs project are found in your package.json's scripts object (property);


![package.json scripts](https://user-images.githubusercontent.com/34817010/71543032-712ae780-296e-11ea-9940-e893bc6d5fd4.png)

Some of these commands will be used when we dockerize our application. 

**NOW WE WRITE OUR Dockerfile**


```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 


3. 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;

![final project] (https://user-images.githubusercontent.com/34817010/71544162-d802cd80-297b-11ea-8e00-f04ba8a7ffed.JPG)


4. 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;


```dockerfile
# 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;

```dockerfile
# 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