Dockerize Nodejs and Postgres example
Tags: javascript, devops, webdev, docker
Published: 2023-10-19 02:35:36

<a href="https://www.docker.com/">Docker</a> provides lightweight containers to run services in isolation from our infrastructure so we can deliver software quickly. In this tutorial, I will show you how to dockerize Nodejs Express and Postgres example using <a href="https://docs.docker.com/compose/">Docker Compose</a>.

Related Posts:
- <a href="https://www.bezkoder.com/node-express-sequelize-postgresql/">Node.js Express & PostgreSQL Rest APIs example</a>
- <a href="https://www.bezkoder.com/node-js-pagination-postgresql/">Node.js Express Pagination with PostgreSQL example</a>
- <a href="https://www.bezkoder.com/typescript-orm-postgres/">Node.js Typescript Rest API with Postgres example</a>
- <a href="https://www.bezkoder.com/node-js-jwt-authentication-postgresql/">Node.js JWT Authentication & Authorization with PostgreSQL example</a>
- <a href="https://www.bezkoder.com/node-js-csv-postgresql/">Import CSV data into PostgreSQL using Node.js</a>
- <a href="https://www.bezkoder.com/node-js-export-postgresql-csv-file/">Export PostgreSQL data to CSV file using Node.js</a>

<h2>Dockerizing Node.js and Postgres Overview</h2>
Assume that we have a Nodejs Application working with Postgres database.
The problem is to containerize a system that requires more than one Docker container:
<ul>
	<li>Node.js Express for API</li>
	<li>PostgreSQL for database</li>
</ul>

Docker Compose helps us setup the system more easily and efficiently than with only Docker. We're gonna following these steps:
<ul>
	<li>Create Nodejs App working with Postgres database.</li>
	<li>Create Dockerfile for Nodejs App.</li>
	<li>Write Docker Compose configurations in YAML file.</li>
	<li>Set Environment variables for Docker Compose</li>
	<li>Run the system.</li>
</ul>

Directory Structure:

<img src="https://www.bezkoder.com/wp-content/uploads/docker-compose-nodejs-postgres-example.png" alt="docker-compose-nodejs-postgres-example" width="160" height="170" class="alignnone size-full wp-image-15901" />

<h2>Create Nodejs App</h2>
You can read and get Github source code from one of following tutorials:
- <a href="https://www.bezkoder.com/node-express-sequelize-postgresql/">Node.js Express & PostgreSQL Rest APIs example</a>
- <a href="https://www.bezkoder.com/node-js-pagination-postgresql/">Node.js Express Pagination with PostgreSQL example</a>
- <a href="https://www.bezkoder.com/typescript-orm-postgres/">Node.js Typescript Rest API with Postgres example</a>
- <a href="https://www.bezkoder.com/node-js-jwt-authentication-postgresql/">Node.js JWT Authentication & Authorization with PostgreSQL example</a>
- <a href="https://www.bezkoder.com/node-js-csv-postgresql/">Import CSV data into PostgreSQL using Node.js</a>
- <a href="https://www.bezkoder.com/node-js-export-postgresql-csv-file/">Export PostgreSQL data to CSV file using Node.js</a>

Using the code base above, we put the Nodejs project in <strong>bezkoder-app</strong> folder and modify some files to work with environment variables.

Firstly, let's add <code>dotenv</code> module into <em>package.json</em>.

```json
{
  ...
  "dependencies": {
    "dotenv": "^10.0.0",
    ...
  }
}
```

Next we import <code>dotenv</code> in <em>server.js</em> and use <code>process.env</code> for setting port.

```js
require("dotenv").config();
..
// set port, listen for requests
const PORT = process.env.NODE_DOCKER_PORT || 8080;
app.listen(PORT, () => {
  console.log(`Server is running on port ${PORT}.`);
});
```

Then we change modify database configuration and initialization.

<strong>app</strong>/<strong>config</strong>/<em>db.config.js</em>
```js
module.exports = {
  HOST: process.env.DB_HOST,
  USER: process.env.DB_USER,
  PASSWORD: process.env.DB_PASSWORD,
  DB: process.env.DB_NAME,
  port: process.env.DB_PORT,
  dialect: "postgres",
  pool: {
    max: 5,
    min: 0,
    acquire: 30000,
    idle: 10000
  }
};
```

<strong>app</strong>/<strong>models</strong>/<em>index.js</em>
```js
const dbConfig = require("../config/db.config.js");

const Sequelize = require("sequelize");
const sequelize = new Sequelize(dbConfig.DB, dbConfig.USER, dbConfig.PASSWORD, {
  host: dbConfig.HOST,
  dialect: dbConfig.dialect,
  port: dbConfig.port,
  operatorsAliases: false,

  pool: {
    max: dbConfig.pool.max,
    min: dbConfig.pool.min,
    acquire: dbConfig.pool.acquire,
    idle: dbConfig.pool.idle
  }
});

...
```

We also need to make a <em>.env</em> sample file that shows all necessary arguments.

<strong>bezkoder-app</strong>/<em>.env.sample</em>
```properties
DB_HOST=localhost
DB_USER=postgres
DB_PASSWORD=123456
DB_NAME=bezkoder_db
DB_PORT=5432

NODE_DOCKER_PORT=8080
```

<h2>Create Dockerfile for Nodejs App</h2>
Dockerfile defines a list of commands that Docker uses for setting up the Node.js application environment. So we put the file in <strong>bezkoder-app</strong> folder.

Because we will use Docker Compose, we won't define all the configuration commands in this Dockerfile.

<strong>bezkoder-app</strong>/<em>Dockerfile</em>
```properties
FROM node:14

WORKDIR /bezkoder-app
COPY package.json .
RUN npm install
COPY . .
CMD npm start
```

Let me explain some points:
<ul>
	<li><code>FROM</code>: install the image of the Node.js version.</li>
	<li><code>WORKDIR</code>: path of the working directory.</li>
	<li><code>COPY</code>: copy <em>package.json</em> file to the container, then the second one copies all the files inside the project directory.</li>
	<li><code>RUN</code>: execute a command-line inside the container: <code>npm install</code> to install the dependencies in <em>package.json</em>.</li>
	<li><code>CMD</code>: run script <code>npm start</code> after the image is built.</li>
</ul>

<h2>Write Docker Compose configurations</h2>
Let's make Nodejs connect with PostgreSQL using Docker.

On the root of the project directory, we're gonna create the <em>docker-compose.yml</em> file. Follow <a href="https://docs.docker.com/compose/compose-file/compose-file-v3/">version 3</a> syntax defined by Docker:

```properties
version: '3.8'

services: 
    postgresdb:
    app:

volumes:
```
<ul>
	<li><code>version</code>: Docker Compose file format version will be used.</li>
	<li><code>services</code>: individual services in isolated containers. Our application has two services: <code>app</code> (Nodejs) and <code>postgresdb</code> (Postgres database).</li>
	<li><code><a href="https://docs.docker.com/storage/volumes/">volumes</a></code>: named volumes that keeps our data alive after restart.</li>
</ul>

Let's implement the details.

<em>docker-compose.yml</em>
```properties
version: '3.8'

services:
  postgresdb:
    image: postgres
    restart: unless-stopped
    env_file: ./.env
    environment:
      - POSTGRES_USER=$POSTGRESDB_USER
      - POSTGRES_PASSWORD=$POSTGRESDB_ROOT_PASSWORD
      - POSTGRES_DB=$POSTGRESDB_DATABASE
    ports:
      - $POSTGRESDB_LOCAL_PORT:$POSTGRESDB_DOCKER_PORT
    volumes:
      - db:/var/lib/postgres
  app:
    depends_on:
      - postgresdb
    build: ./bezkoder-app
    restart: unless-stopped
    env_file: ./.env
    ports:
      - $NODE_LOCAL_PORT:$NODE_DOCKER_PORT
    environment:
      - DB_HOST=postgresdb
      - DB_USER=$POSTGRESDB_USER
      - DB_PASSWORD=$POSTGRESDB_ROOT_PASSWORD
      - DB_NAME=$POSTGRESDB_DATABASE
      - DB_PORT=$POSTGRESDB_DOCKER_PORT
    stdin_open: true
    tty: true

volumes: 
  db:
```

- <strong>postgresdb</strong>:
<ul>
	<li><code>image</code>: official Docker image</li>
	<li><code>restart</code>: configure the <a href="https://docs.docker.com/config/containers/start-containers-automatically/#use-a-restart-policy">restart policy</a></li>
	<li><code>env_file</code>: specify our <em>.env</em> path that we will create later</li>
	<li><code>environment</code>: provide setting using environment variables</li>
	<li><code>ports</code>: specify ports will be used</li>
	<li><code>volumes</code>: map volume folders</li>
</ul>

- <strong>app</strong>:
<ul>
	<li><code><a href="https://docs.docker.com/compose/compose-file/compose-file-v3/#depends_on">depends_on</a></code>: dependency order, <strong>postgresdb</strong> is started before <strong>app</strong></li>
	<li><code>build</code>: configuration options that are applied at build time that we defined in the <em>Dockerfile</em> with relative path</li>
	<li><code>environment</code>: environmental variables that Node application uses</li>
	<li><code>stdin_open</code> and <code>tty</code>: keep open the terminal after building container</li>
</ul>

You should note that the host port (<code>LOCAL_PORT</code>) and the container port (<code>DOCKER_PORT</code>) is different. Networked service-to-service communication uses the container port, and the outside uses the host port.

<h2>Docker Compose Environment variables with Postgres</h2>
In the service configuration, we used environmental variables defined inside the <em>.env</em> file. Now we start writing it.

<em>.env</em>
```properties
POSTGRESDB_USER=postgres
POSTGRESDB_ROOT_PASSWORD=123456
POSTGRESDB_DATABASE=bezkoder_db
POSTGRESDB_LOCAL_PORT=5433
POSTGRESDB_DOCKER_PORT=5432

NODE_LOCAL_PORT=6868
NODE_DOCKER_PORT=8080
```

<h2>Run Nodejs Postgres with Docker Compose</h2>
We can easily run the whole with only a single command:
<code>docker compose up</code>

Docker will pull the PostgreSQL and Node.js images (if our machine does not have it before).

The services can be run on the background with command:
<code>docker compose up -d</code>

```bash
$ docker compose up -d
[+] Running 14/14
 ✔ postgresdb 13 layers [⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿]      0B/0B      Pulled                                                                                                                                                              
   ✔ a378f10b3218 Pull complete                                                                                                                                                                                              
   ✔ 2ebc5690e391 Pull complete                                                                                                                                                                                              
   ✔ 8fe57f734687 Pull complete                                                                                                                                                                                              
   ✔ a2ddbb09cd9a Pull complete                                                                                                                                                                                             
   ✔ 5a2499e87ab8 Pull complete                                                                                                                                                                                             
   ✔ a45f5c4adf1b Pull complete                                                                                                                                                                                             
   ✔ 178017fd978e Pull complete                                                                                                                                                                                             
   ✔ 428dff1cb77d Pull complete                                                                                                                                                                                             
   ✔ 4667364adfc4 Pull complete                                                                                                                                                                                             
   ✔ 4eea1f5281a9 Pull complete                                                                                                                                                                                             
   ✔ 369467411787 Pull complete                                                                                                                                                                                             
   ✔ 51184495a2bc Pull complete                                                                                                                                                                                             
   ✔ d3e246f01410 Pull complete                                                                                                                                                                                             
[+] Building 77.4s (10/10) FINISHED                                                                                                                                                                                docker:default
 => [app internal] load build definition from Dockerfile                                                                                                                                                                     
 => => transferring dockerfile: 179B                                                                                                                                                                                         
 => [app internal] load .dockerignore                                                                                                                                                                                        
 => => transferring context: 2B                                                                                                                                                                                              
 => [app internal] load metadata for docker.io/library/node:14                                                                                                                                                              
 => [app 1/5] FROM docker.io/library/node:14@sha256:a158d3b9b4e3fa813fa6c8c590b8f0a860e015ad4e59bbce5744d2f6fd8461aa                                                                                                        
 => => resolve docker.io/library/node:14@sha256:a158d3b9b4e3fa813fa6c8c590b8f0a860e015ad4e59bbce5744d2f6fd8461aa                                                                                                             
 => => sha256:3d2201bd995cccf12851a50820de03d34a17011dcbb9ac9fdf3a50c952cbb131 10.00MB / 10.00MB                                                                                                                             
 => => sha256:2cafa3fbb0b6529ee4726b4f599ec27ee557ea3dea7019182323b3779959927f 2.21kB / 2.21kB                                                                                                                               ...
 => => extracting sha256:0c8cc2f24a4dcb64e602e086fc9446b0a541e8acd9ad72d2e90df3ba22f158b3                                                                                                                                    
 => => extracting sha256:0d27a8e861329007574c6766fba946d48e20d2c8e964e873de352603f22c4ceb                                                                                                                                    
 => [app internal] load build context                                                                                                                                                                                        
 => => transferring context: 17.47kB                                                                                                                                                                                         
 => [app 2/5] WORKDIR /bezkoder-app                                                                                                                                                                                          
 => [app 3/5] COPY package.json .                                                                                                                                                                                            
 => [app 4/5] RUN npm install                                                                                                                                                                                               
 => [app 5/5] COPY . .                                                                                                                                                                                                       
 => [app] exporting to image                                                                                                                                                                                                 
 => => exporting layers                                                                                                                                                                                                      
 => => writing image sha256:4571b91ea16b395b5e00833cc5770695f07250627ebceee1d5125660b93abd00                                                                                                                                 
 => => naming to docker.io/library/node-postgres-app                                                                                                                                                                         
[+] Running 4/4
 ✔ Network node-postgres_default         Created                                                                                                                                                                             
 ✔ Volume "node-postgres_db"             Created                                                                                                                                                                             
 ✔ Container node-postgres-postgresdb-1  Started                                                                                                                                                                             
 ✔ Container node-postgres-app-1         Started     
```

Now you can check the current working containers:

```bash
$ docker ps
CONTAINER ID   IMAGE               COMMAND                  CREATED         STATUS         PORTS                                       NAMES
4b781fa774f6   node-postgres-app   "docker-entrypoint.s…"   2 minutes ago   Up 2 minutes   0.0.0.0:6868->8080/tcp, :::6868->8080/tcp   node-postgres-app-1
d268e1be7e80   postgres            "docker-entrypoint.s…"   2 minutes ago   Up 2 minutes   0.0.0.0:5433->5432/tcp, :::5433->5432/tcp   node-postgres-postgresdb-1
```

And Docker images:

```bash
$ docker images
REPOSITORY            TAG       IMAGE ID       CREATED         SIZE
node-postgres-app     latest    4571b91ea16b   3 minutes ago   943MB
postgres              latest    f7d9a0d4223b   4 weeks ago     417MB
```

Send a HTTP request to the Nodejs - Postgres application:

<img src="https://www.bezkoder.com/wp-content/uploads/docker-compose-nodejs-postgres.png" alt="docker-compose-nodejs-postgres" width="600" height="450" class="alignnone size-full wp-image-15903" />

Check Postgres Database:

```bash
$ docker exec -ti node-postgres-postgresdb-1 /bin/bash
root@eba893d13a10:/# psql bezkoder_db postgres
psql (16.0 (Debian 16.0-1.pgdg120+1))
Type "help" for help.

bezkoder_db=# SELECT * FROM tutorials;
 id |          title          |    description    | published |         createdAt          |         updatedAt          
----+-------------------------+-------------------+-----------+----------------------------+----------------------------
  1 | Docker Postgres Express | Tut#1 Description | f         | 2023-10-14 10:13:17.432+00 | 2023-10-14 10:13:17.432+00
(1 row)
```

<h2>Stop the Application</h2>
Stopping all the running containers is also simple with a single command:
<code>docker compose down</code>

```bash
$ docker compose down
 ✔ Container node-postgres-app-1         Removed                                                                                                                                                                  
 ✔ Container node-postgres-postgresdb-1  Removed                                                                                                                                                                   
 ✔ Network node-postgres_default         Removed     
```

If you need to stop and remove all containers, networks, and all images used by any service in <em>docker-compose.yml</em> file, use the command:
<code>docker compose down --rmi all</code>

<h2>Conclusion</h2>
Today we've successfully created Docker Compose file for Postgres and Nodejs application. Now we can deploy Nodejs Express and Postgres with Docker on a very simple way: <em>docker-compose.yml</em>.

You can apply this way to one of following project:
- <a href="https://www.bezkoder.com/node-express-sequelize-postgresql/">Node.js Express & PostgreSQL Rest APIs example</a>
- <a href="https://www.bezkoder.com/node-js-pagination-postgresql/">Node.js Express Pagination with PostgreSQL example</a>
- <a href="https://www.bezkoder.com/typescript-orm-postgres/">Node.js Typescript Rest API with Postgres example</a>
- <a href="https://www.bezkoder.com/node-js-jwt-authentication-postgresql/">Node.js JWT Authentication & Authorization with PostgreSQL example</a>

Happy Learning! See you again.

<h2>Source Code</h2>
The source code for this tutorial can be found at <a href="https://github.com/bezkoder/docker-compose-nodejs-postgres">Github</a>.

If you want to add Comments for each Tutorial. It is the One-to-Many Relationship there is a tutorial:
<a href="https://www.bezkoder.com/sequelize-associate-one-to-many/">Node.js Sequelize Associations: One-to-Many example</a>
(the tutorial uses MySQL but you can easily change the configuration for PostgreSQL)

Or you can add Tags for each Tutorial and add Tutorials to Tag (Many-to-Many Relationship):
<a href="https://www.bezkoder.com/sequelize-associate-many-to-many/">Node.js Sequelize Associations: Many-to-Many example</a>