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:
- Node.js Express & PostgreSQL Rest APIs example
- Node.js Express Pagination with PostgreSQL example
- Node.js Typescript Rest API with Postgres example
- Node.js JWT Authentication & Authorization with PostgreSQL example
- Import CSV data into PostgreSQL using Node.js
- Export PostgreSQL data to CSV file using Node.js
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:
Create Nodejs App
You can read and get Github source code from one of following tutorials:
- Node.js Express & PostgreSQL Rest APIs example
- Node.js Express Pagination with PostgreSQL example
- Node.js Typescript Rest API with Postgres example
- Node.js JWT Authentication & Authorization with PostgreSQL example
- Import CSV data into PostgreSQL using Node.js
- Export PostgreSQL data to CSV file using Node.js
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",
...
}
}
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}.`);
});
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
}
};
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
}
});
...
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
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
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 scriptnpm 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:
-
version
: Docker Compose file format version will be used. -
services
: individual services in isolated containers. Our application has two services:app
(Nodejs) andpostgresdb
(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:
-
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
andtty
: 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
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
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
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
Send a HTTP request to the Nodejs - Postgres application:
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)
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
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:
- Node.js Express & PostgreSQL Rest APIs example
- Node.js Express Pagination with PostgreSQL example
- Node.js Typescript Rest API with Postgres example
- Node.js JWT Authentication & Authorization with PostgreSQL example
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