DEV Community

Abraão Duarte
Abraão Duarte

Posted on

Simple Node Application with Docker + Nginx + Postgres + Docker Compose

Image description

Hi everyone, this is my first time trying to create a small tutorial where I will show how to run a Node application with Docker, Nginx with load balancing, Postgres, and Docker Compose. If you have any feedback at the end, please let me know; I will appreciate it. Also, English is not my native language, so if you find grammar errors, sorry about it.

I will assume that you have Docker and Docker Compose installed on your machine. This tutorial was made on Ubuntu OS.

If you don't have Docker and Docker Compose installed, you can visit the Docker website to see how to install Docker. For Docker Compose, you can check this link: Docker Compose.

Let's get down to business.

1 - Create a route using Hono.js

Hono.js is a nice framework that helps us build Node apps. This article does not aim to teach Hono.js; I will create a simple route to print Hello World!

First, create a new folder on your machine using the command line:

~ mkdir example
Enter fullscreen mode Exit fullscreen mode

Enter your folder and let's install some libraries. I will be using pnpm, a package manager like npm or yarn but with some differences. If you don't have it installed, you can access the straightforward instructions in the documentation: pnpm

Install hono and typescrypt

~ pnpm init // To create the initial package.json
~ pnpm install hono
~ pnpm install @hono/node-server
~ pnpm install -D typescript
Enter fullscreen mode Exit fullscreen mode

After installing these packages, you should run the command below to create a default tsconfig.json, which is the TypeScript configuration of the project.

~ npx tsc --init
Enter fullscreen mode Exit fullscreen mode

Now create a folder called src, and inside it, create the file server.ts:

~ mkdir src
~ touch src/server.ts
Enter fullscreen mode Exit fullscreen mode

In the server.ts add the code below.

1 import { Hono } from "hono";
2 import { serve } from "@hono/node-server";
3 import { logger } from "hono/logger";
4
5 const Server = {
6     start: () => {
7         const app = new Hono();
8         app.use(logger());
9 
10        app.get("/", (c) => c.text("Hello World!"));
11
12        serve(
13            {
14                port: 3000,
15                fetch: app.fetch,
16            },
17            (info) => {
18                console.log(`Listening on http://localhost:${info.port}`);
19            },
20        );
21    },
22 };
23
24 Server.start();

Enter fullscreen mode Exit fullscreen mode

Here we have a simple server that will provide an endpoint returning "Hello World!".

You should install the tsx package to run the server:

~ pnpm install -D tsx
Enter fullscreen mode Exit fullscreen mode

And in the package.json under the scripts section, we add this line:

"start": "npx tsx ./src/server.ts"
Enter fullscreen mode Exit fullscreen mode

package.json

Now in your terminal you can run:

~ pnpm start
Enter fullscreen mode Exit fullscreen mode

Now our app is running. You just need to open your browser and go to the address http://localhost:3000/ and you will see the page with the Hello World!.

2 Configure docker for our server

Now we will configure a Dockerfile for our app. I will not explain the Docker syntax in detail, but this is a simple Dockerfile.

In the root of the project create a Dockerfile

~ touch Dockerfile
Enter fullscreen mode Exit fullscreen mode

Now put this code inside the Dockerfile:

1 FROM node:22-alpine
2
3 COPY . /app
4
5 WORKDIR /app
6
7 RUN npm install -g pnpm
8
9 RUN pnpm install
10
11 EXPOSE 3000
12
13 CMD ["pnpm", "start"]

Enter fullscreen mode Exit fullscreen mode

1 - FROM - This will tell the name of the image that we want to use.
2 - COPY - We are making a copy of everything from our project and putting in the app folder from our container.
3 - Workdir - We tell the container from what folder we want to execute our commands.
4 - RUN - We use this to install dependencies inside our container, in this case we are installing pnpm.
5 - EXPOSE - Here we say what port our container will use. PS: Is not the port of our machine (host).
6 - CMD - We pass which commands the container should run at the end.

Now create the build from our dockerfile:

~ docker build -t my-server:1.0 . // You can put the name you want
Enter fullscreen mode Exit fullscreen mode

The dot . is for the command run the Dockerfile from the folder you are.

If you want to see the container just use the command:

~ docker ps -a
Enter fullscreen mode Exit fullscreen mode

Now run the container with this command:

~ docker run my-server:1.0
Enter fullscreen mode Exit fullscreen mode

Access the http://localhost:3000/ you will see a problem:

Image description

We cannot access the address because the app is running only inside the container, and we didn't specify which port from our machine the container should use.

Let's try again, but now mapping the port from the container to a port on our machine (host):

~ docker run -p 3000:3000 my-server:1.0 
Enter fullscreen mode Exit fullscreen mode

Now if you access the http://localhost:3000/ you'll see that the app running because we told Docker to map port 3000 from the container to port 3000 on our machine. PS: You could use another port here if you want.

Now the app is working with Docker; let's configure Nginx.

3 - Configure Nginx with load balancing

Nginx is an open-source web server software used for reverse proxy, load balancing, and caching.

In the root of the project, create a new folder called nginx , and inside it, create the Dockerfile.

~ mkdir nginx
~ touch nginx/Dockerfile
Enter fullscreen mode Exit fullscreen mode

Before we configure Nginx, let's see it working on our machine. Put this code inside the Dockerfile.

1 FROM nginx:latest
2
3 EXPOSE 80
Enter fullscreen mode Exit fullscreen mode

Here we are getting the latest image from nginx from Dockerhub and exposing the port 80 from the container. Now we can build and run it. Go to the nginx folder in your terminal and run:

~ docker build -t my-server-nginx:1.0 . // My container will have the name my-server-nginx:1.0
~ docker run -p 9000:80 my-server-nginx:1.0
Enter fullscreen mode Exit fullscreen mode

The second command runs our Nginx, listening to port 9000 on our machine. If you access http://localhost:9000/ you'll see the page:

Image description

Now let's configure load balancing for our Nginx. First, inside the nginx folder, create nginx.conf

~ touch nginx.conf
Enter fullscreen mode Exit fullscreen mode

Let's edit the ´nginx.conf' and add these lines:

Image description

The upstream backend is responsible for our load balancing. Load balancing is a technique that distributes network traffic across multiple servers to prevent bottlenecks and improve performance. For more information, you can visit this link.

PS: The proxy_pass domain (line 18) should match the name you put in the upstream (line 7), in this case, backend.

In the upstream, we specify that we'll have two servers. Every time we access http://myserver.local Nginx will redirect the request to one of these two servers, preventing server overload.

In the Nginx Dockerfile, we can add a new line after the instruction FROM:

COPY ./nginx.conf /etc/nginx/nginx.conf
Enter fullscreen mode Exit fullscreen mode

Your Dockerfile should look like this:

Image description

To make it easy to run everything together, let's configure Docker Compose.

4 - Configure Docker Compose

In the root of the project, create docker-compose.yml and add the code below:

version: '3.9'

services:
  app:
    build: 
      context: .
    volumes:
      - ./:/usr/app
      - /usr/app/node_modules
    ports:
      - 3000:3000
    networks:
      - app-network

  app2:
    build: 
      context: .
    volumes:
      - ./:/usr/app
      - /usr/app/node_modules
    ports:
      - 3001:3000
    networks:
      - app-network

  nginx:
    build:
      context: ./nginx/.
    container_name: my-server-nginx
    ports:
      - "80:80"
    depends_on:
      - app
      - app2
    networks:
      - app-network

networks:
  app-network:
    driver: bridge
Enter fullscreen mode Exit fullscreen mode

Note that we have two instances or containers for our server, app and app2. If you change these names, you'll need to change the name in the nginx.conf inside the upstream.

Also, in nginx.conf inside the upstream, you should put the port that you exposed in your container, in our case, 3000. I don't know why, but when I tried to put the port of my machine, it didn't work.

To access http://myserver.local/ you will need to edit your /etc/hosts file.

~ sudo vim /etc/hosts
Enter fullscreen mode Exit fullscreen mode

At the end of the file, add this line:

127.0.0.1 myserver.local
Enter fullscreen mode Exit fullscreen mode

Now save the file and try to exit from vim 🤣

In your terminal run:

~ docker-compose up
Enter fullscreen mode Exit fullscreen mode

This command will start the apps and Nginx. Now you can access the route at http://myserver.local/, and every time you refresh the page, you will see that the request is sent to one of the servers, sometimes to app and sometimes to app2.

Image description

5 - Configure Postgres

I will not connect the app with the database; I will show just how to set up Postgres, and you can connect as you wish.

In our docker-compose.yml, add the following lines after the Nginx section:

postgres:
    container_name: postgres
    restart: always
    image: postgres
    ports:
      - 5432:5432
    environment:
      POSTGRES_USER: docker
      POSTGRES_PASSWORD: postgres
      POSTGRES_DB: myserver
    networks:
      - app-network
    volumes:
       - postgres:/data/postgres


volumes:
    postgres:
Enter fullscreen mode Exit fullscreen mode

You can stop the containers using this command and start again:

~ docker-compose down // stop the containers
~ docker-compose up
Enter fullscreen mode Exit fullscreen mode

To view your database, you can install dbeaver I have been using DBeaver for a long time (thanks bop for recommending it 🙏🏻).

Notice that we have three environment variables that Docker will use. You can change their values if you want.

In DBeaver, you can click on new database connection, select Postgres, and fill in the database, password, and user fields with the values with environment variables in the docker-compose.yml.

Image description

Notice that the:
1 - host = localhost # default
2 - username = docker # This comes from POSTGRES_USER inside docker-compose.yml
3 - password = postgres # This comes from POSTGRES_PASSWROD inside docker-compose.yml
4 - database = myserver # This comes from POSTGRES_DB inside docker-compose.yml

With all this, you will have a simple start project with hono.js + nginx + postgres + docker.

And that's it, folks! Thank you for reading this far. If you liked the post, please give it a like.

If you have feedback or questions, let me know.

Repository: github

SDG

Top comments (0)