Dockerize Nodejs and Postgres example

Tien Nguyen - Oct 19 '23 - - Dev Community

Docker 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 Docker Compose.

Related Posts:

Dockerizing Node.js and Postgres Overview

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:

  • Node.js Express for API
  • PostgreSQL for database

Docker Compose helps us setup the system more easily and efficiently than with only Docker. We're gonna following these steps:

  • Create Nodejs App working with Postgres database.
  • Create Dockerfile for Nodejs App.
  • Write Docker Compose configurations in YAML file.
  • Set Environment variables for Docker Compose
  • Run the system.

Directory Structure:

docker-compose-nodejs-postgres-example

Create Nodejs App

You can read and get Github source code from one of following tutorials:

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

Firstly, let's add dotenv module into package.json.



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


Enter fullscreen mode Exit fullscreen mode

Next we import dotenv in server.js and use process.env for setting port.



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}.`);
});


Enter fullscreen mode Exit fullscreen mode

Then we change modify database configuration and initialization.

app/config/db.config.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
  }
};


Enter fullscreen mode Exit fullscreen mode

app/models/index.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
  }
});

...


Enter fullscreen mode Exit fullscreen mode

We also need to make a .env sample file that shows all necessary arguments.

bezkoder-app/.env.sample



DB_HOST=localhost
DB_USER=postgres
DB_PASSWORD=123456
DB_NAME=bezkoder_db
DB_PORT=5432

NODE_DOCKER_PORT=8080


Enter fullscreen mode Exit fullscreen mode

Create Dockerfile for Nodejs App

Dockerfile defines a list of commands that Docker uses for setting up the Node.js application environment. So we put the file in bezkoder-app folder.

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

bezkoder-app/Dockerfile



FROM node:14

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


Enter fullscreen mode Exit fullscreen mode

Let me explain some points:

  • FROM: install the image of the Node.js version.
  • WORKDIR: path of the working directory.
  • COPY: copy package.json file to the container, then the second one copies all the files inside the project directory.
  • RUN: execute a command-line inside the container: npm install to install the dependencies in package.json.
  • CMD: run script npm start after the image is built.

Write Docker Compose configurations

Let's make Nodejs connect with PostgreSQL using Docker.

On the root of the project directory, we're gonna create the docker-compose.yml file. Follow version 3 syntax defined by Docker:



version: '3.8'

services: 
    postgresdb:
    app:

volumes:


Enter fullscreen mode Exit fullscreen mode
  • version: Docker Compose file format version will be used.
  • services: individual services in isolated containers. Our application has two services: app (Nodejs) and postgresdb (Postgres database).
  • volumes: named volumes that keeps our data alive after restart.

Let's implement the details.

docker-compose.yml



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:


Enter fullscreen mode Exit fullscreen mode
  • postgresdb:

    • image: official Docker image
    • restart: configure the restart policy
    • env_file: specify our .env path that we will create later
    • environment: provide setting using environment variables
    • ports: specify ports will be used
    • volumes: map volume folders
  • app:

    • depends_on: dependency order, postgresdb is started before app
    • build: configuration options that are applied at build time that we defined in the Dockerfile with relative path
    • environment: environmental variables that Node application uses
    • stdin_open and tty: keep open the terminal after building container

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

Docker Compose Environment variables with Postgres

In the service configuration, we used environmental variables defined inside the .env file. Now we start writing it.

.env



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


Enter fullscreen mode Exit fullscreen mode

Run Nodejs Postgres with Docker Compose

We can easily run the whole with only a single command:
docker compose up

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:
docker compose up -d



$ 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     


Enter fullscreen mode Exit fullscreen mode

Now you can check the current working containers:



$ 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


Enter fullscreen mode Exit fullscreen mode

And Docker images:



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


Enter fullscreen mode Exit fullscreen mode

Send a HTTP request to the Nodejs - Postgres application:

docker-compose-nodejs-postgres

Check Postgres Database:



$ 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)


Enter fullscreen mode Exit fullscreen mode

Stop the Application

Stopping all the running containers is also simple with a single command:
docker compose down



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


Enter fullscreen mode Exit fullscreen mode

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

Conclusion

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: docker-compose.yml.

You can apply this way to one of following project:

Happy Learning! See you again.

Source Code

The source code for this tutorial can be found at Github.

If you want to add Comments for each Tutorial. It is the One-to-Many Relationship there is a tutorial:
Node.js Sequelize Associations: One-to-Many example
(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):
Node.js Sequelize Associations: Many-to-Many example

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .