API with NestJS #91. Dockerizing a NestJS API with Docker Compose

JavaScript NestJS

This entry is part 91 of 168 in the API with NestJS

So far, in this series of articles, we’ve been using Docker to run various services our NestJS application depends on. Some good examples are PostgreSQL and Redis. We can take it a step further and put our NestJS application into a Docker container as well. It can be very useful when looking into deploying our app.

You can find the code from this article in this repository.

Building the Docker image

We need to start by building a Docker image for our NestJS application. A Docker image acts as a template that describes everything our application needs to run. For example, it includes the source code and its JavaScript dependencies. Besides the above, it also contains information about the desired operating system and its components, such as the Node.js version.

There are a lot of Docker images ready to use and maintained by the community. We can find them on Docker Hub. A good example is , based on the lightweight Alpine Linux distribution that comes with Node.js preinstalled.

To define a Docker image, we need to create the . It’s a text file containing all the information about the image. When building a Docker image for our application, we can base it on an existing one. To do that, we need to start our with the keyword and specify the parent image from which we’re building our new image.

Dockerfile

We can now specify additional instructions to make sure our NestJS application will be able to run correctly. By default, Docker will run all of the following commands in the root directory. To keep our Docker container tidy, we can use the instruction to define the working directory for all the following commands.

Dockerfile

So far, our Docker image does not contain the source code of our NestJS application. Let’s add it and install the dependencies.

Dockerfile

is an alternative for meant for automated environments. By running it with the flag we avoid installing packages listed in the to achieve a smaller Docker image. We need to make sure that we list all of the packages the image needs such as in the section.

By running , we copy all of the files from our application to the directory in our Docker image. Then, we execute to install the necessary JavaScript dependencies.

Once the above process is ready, we can build and start our NestJS application. Our complete looks like that:

Dockerfile

We run to avoid running our application as root for security reasons. It might also be a good idea to change the owner of the files we’ve copied before.

It’s crucial to understand the difference between the and instructions. The command defines the image build step and will be executed when building the image before starting the application. Therefore, we can have multiple instructions in a single .

The instruction does not run when building the image. Instead, Docker executes it once when running the Docker container created based on our image. Therefore, using more than one instruction causes the last one to override the previous commands.

Ignoring some of the files in our application

By default, copies all of the files from the directory of our application into the Docker image. However, we need to remember that there are some files we shouldn’t put into our Docker image. To define them, we need to create the file.

.dockerignore

Building the Docker image

Once we’ve created our , we need to build our Docker image.

Sending build context to Docker daemon 856.1kB
Step 1/7 : FROM node:18-alpine
—> 264f8646c2a6
Step 2/7 : WORKDIR /user/src/app
—> Running in a157ed686647
Removing intermediate container a157ed686647
—> 665415e6101e
Step 3/7 : COPY . .
—> 6ba70bc9f752
Step 4/7 : RUN npm ci –omit=dev
—> Running in 4206995663aa

added 565 packages, and audited 566 packages in 17s

59 packages are looking for funding
run npm fund for details

found 0 vulnerabilities
Removing intermediate container 4206995663aa
—> c98bfbb842ec
Step 5/7 : RUN npm run build
—> Running in af3ae30c58da

> nest-typescript-starter@1.0.0 prebuild
> rimraf dist

> nest-typescript-starter@1.0.0 build
> nest build

Removing intermediate container af3ae30c58da
—> 26021dcbe202
Step 6/7 : USER node
—> Running in bbfe5d194ada
Removing intermediate container bbfe5d194ada
—> ad1331df0fa1
Step 7/7 : CMD [“npm”, “run”, “start:prod”]
—> Running in 1691c04e966b
Removing intermediate container 1691c04e966b
—> 1268ba0ec302
Successfully built 1268ba0ec302
Successfully tagged nestjs-api:latest

The crucial part of the above process is that Docker ran and  but didn’t run  yet.

By adding we’ve chosen a name for our Docker image. Thanks to that, we will be able to refer to it later.

Using Docker Compose to run multiple containers

Once we’ve built a Docker image, we can run a Docker container based on it. The container is a runtime instance of the image.

We could run our image with a single command:

The above command creates a Docker container and executes inside.

Often an application consists of multiple Docker containers, though. In our case, we want to run PostgreSQL and let our NestJS API connect with it. Maintaining multiple containers and ensuring they can communicate with each other can be challenging.

Fortunately, we can use the Docker Compose tool that helps us run multi-container Docker applications. When working with it, we need to definite the configuration of our application using YAML.

docker-compose.yml

A few significant things are happening above, so let’s break the file down.

Creating a custom network

We create a custom network by adding the entry in the section.

Thanks to the above, we can easily specify which Docker containers can communicate with each other.

Setting up PostgreSQL

The first Docker container we specify in the section uses the image.

It’s always a good idea to use a specific version instead of .

You can see above that we add it to the network so that other Docker containers can communicate with it.

Thanks to configuring , we allow the Docker container to persist data outside the container. Thanks to doing that, once we shut down the container and rerun it, we don’t end up with an empty database.

By specifying the property, we can provide a set of environment variables the container will use, such as the credentials required to connect to the database.

docker.env

Configuring pgAdmin

pgAdmin is a useful tool to help us manage our PostgreSQL database. Our pgAdmin container runs using the image.

The new thing to grasp in the above code is the section. By default, pgAdmin runs on port . Thanks to adding to the section Docker exposes pgAdmin outside of the Docker container and allows us to access it through on our machine.

To log in, we need to use the credentials provided in the file.

To connect to our PostgresSQL database, we also need to use the credentials provided in the file.

Since both pgAdmin and PostgreSQL run in the Docker containers, we need to provide as the host name. It matches the name of the service specified in the file.

Setting up our NestJS application

The last step is to add our NestJS application to our configuration.

The most crucial part of the above code is the . The name of the Docker image needs to match the command we’ve used before.

By adding to the section, we can provide a list of environment variables for our NestJS application.

.env

As you can see above, our NestJS application runs on port inside the Docker container. We also expect Docker to expose our NestJS API outside of the container. By adding to the section, we specify that we want to be able to reach our NestJS API using .

By adding to the array, we state that our NestJS API shouldn’t run before the PostgreSQL service was initialized.

Running all of the Docker containers

The last step is to run the above Docker Compose configuration.

Once we run Docker Compose, it creates and runs all specified Docker containers. Now our application is up and running. It’s also accessible outside of Docker.

Summary

In this article, we went through the configuration required to run NestJS in a Docker container properly. To do that, we had to learn how to write the and build our Docker image. We also created the Docker Compose configuration that allowed us to run other services our NestJS API needs, such as PostgreSQL. Finally, we also made sure that all of our Docker containers could communicate with each other.

There is still more to learn when it comes to using NestJS with Docker, so stay tuned!

Series Navigation<< API with NestJS #90. Using various types of SQL joinsAPI with NestJS #92. Increasing the developer experience with Docker Compose >>
Subscribe
Notify of
guest
12 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Seb
Seb
1 year ago

great article as always! The above is my dockerfile stitched together from various sources, which has a few advantages

  • usable for development
  • runs on non-root user
  • removes untranspiled(?) code from final image
Ngoc Anh
Ngoc Anh
1 year ago
Reply to  Seb

should n’t copy node_module, should run it on container

nghiahv
nghiahv
1 year ago

hello, how i can run migration inside docker with docker-compose

CoderProfile
CoderProfile
1 year ago
Reply to  nghiahv

Saba
Saba
9 months ago

# Use postgres/example user/password credentials
version: ‘3.1’

services:
db:
image: postgres:latest
container_name: jumpstart_jwt-postgress-container
restart: always
env_file:
.env
environment:
POSTGRES_DB: ${DATABASE_NAME}
POSTGRES_USER: ${DATABASE_USER}
POSTGRES_PASSWORD: ${DATABASE_PASSWORD}
ports:
“${DATABASE_PORT}:5432”
volumes:
postgres_data:/var/lib/postgresql/data
networks:
my_app_network

nestjs-api:
container_name: jumpstart_jwt-api-container
build:
context: .
dockerfile: Dockerfile
ports:
“${APP_PORT}:${APP_PORT}”
env_file:
.env
depends_on:
db
networks:
my_app_network
volumes:
./src:/usr/src/app/src

networks:
my_app_network:
driver: bridge
name: my_app_network
volumes:
postgres_data:

# Installing dependencies:

FROM node:18-alpine AS install-dependencies

WORKDIR /usr/src/app

RUN npm install -g npm@9.5.0

COPY package.json package-lock.json ./

RUN npm install

COPY . .

# Creating a build:

FROM node:18-alpine AS create-build

WORKDIR /usr/src/app

RUN npm install -g npm@9.5.0

COPY –from=install-dependencies /usr/src/app ./

RUN npm run build

USER node

# Running the application:

FROM node:18-alpine AS run

WORKDIR /usr/src/app

RUN npm install -g npm@9.5.0

COPY –from=install-dependencies /usr/src/app/node_modules ./node_modules
COPY –from=create-build /usr/src/app/dist ./dist
COPY package.json ./

RUN npm prune –production

CMD [“npm”, “run”, “start:prod”]

2023-12-10 17:25:14 [Nest] 19 – 12/10/2023, 1:25:14 PM  ERROR [TypeOrmModule] Unable to connect to the database. Retrying (5)…
2023-12-10 17:25:14 Error: connect ECONNREFUSED 127.0.0.1:5432

It seems that nestjs-api container can’t see the postgres container, can’t understand why,

Saba
Saba
9 months ago
Reply to  Marcin Wanago

yes, it works finely on localhost for prod

Saba
Saba
9 months ago
Reply to  Marcin Wanago

Sorry, I can’t understand what you say

Saba
Saba
9 months ago
Reply to  Marcin Wanago

container name is not db, but you say that I should set the container name of postgres as a Host in env for api?

Saba
Saba
9 months ago
Reply to  Marcin Wanago

Thanks a lot, it works, you’re right. I set the wrong host for the container to communicate with another one.

beginner
beginner
6 months ago

I encountered an error while executing the RUN npm run build command. 
This occurred because @nestjs/cli is listed under devDependencies in package.json, and the build command is actually shorthand for nest build
As a result, running npm run build fails after using the npm ci --omit=dev command. For further information, please refer to this page:
https://stackoverflow.com/questions/60555003/getting-an-error-on-dockerising-nest-js-application

I hope this comment proves helpful to someone.