Simple Node Application with Docker + Nginx + Postgres + Docker Compose
Tags: node, docker, nginx, postgres
Published: 2024-06-11 12:07:00

---
title: Simple Node Application with Docker + Nginx + Postgres + Docker Compose
published: true
description: Small tutorial where you will learn how setup a project with node, docker, nginx and postgres
tags: Node, Docker, Nginx, Postgres
# cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/htxv52dcri08nh0zo88j.jpg
# Use a ratio of 100:42 for best results.
# published_at: 2024-06-08 13:16 +0000
---
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/j9cpt9ghace9pu2iprse.jpg)

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](https://docs.docker.com/engine/install/). For Docker Compose, you can check this link: [Docker Compose](https://docs.docker.com/compose/install/).

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:
```bash
~ mkdir example
```

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](https://pnpm.io/pt/installation)

Install hono and typescrypt
```bash
~ pnpm init // To create the initial package.json
~ pnpm install hono
~ pnpm install @hono/node-server
~ pnpm install -D typescript
```

After installing these packages, you should run the command below to create a default `tsconfig.json`, which is the TypeScript configuration of the project.
```bash
~ npx tsc --init
```

Now create a folder called `src`, and inside it, create the file `server.ts`:
```bash
~ mkdir src
~ touch src/server.ts
```

In the `server.ts` add the code below.
```javascript
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();

```
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
```
And in the `package.json` under the scripts section, we add this line:
```json
"start": "npx tsx ./src/server.ts"
```
![package.json](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x01ae2q9iccwvjvx7yo2.png)

Now in your terminal you can run:
```
~ pnpm start
```
Now our app is running. You just need to open your browser and go to the address [http://localhost:3000/](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
```bash
~ touch Dockerfile
```
Now put this code inside the Dockerfile:
```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"]

```
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:
```bash
~ docker build -t my-server:1.0 . // You can put the name you want
```
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:
```bash
~ docker ps -a
``` 

Now run the container with this command:
```bash
~ docker run my-server:1.0
```
Access the [http://localhost:3000/](http://localhost:3000/) you will see a problem: 

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/69unne58iqv1a9se4lip.png)

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 
```
Now if you access the [http://localhost:3000/](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`.
```bash
~ mkdir nginx
~ touch nginx/Dockerfile
```
Before we configure Nginx, let's see it working on our machine. Put this code inside the `Dockerfile`.
```Dockerfile
1 FROM nginx:latest
2
3 EXPOSE 80
```
Here we are getting the latest image from nginx from [Dockerhub](https://hub.docker.com/_/nginx) 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:
```bash
~ 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
```
The second command runs our Nginx, listening to port 9000 on our machine. If you access [http://localhost:9000/](http://localhost:9000/) you'll see the page:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/grkkjyrcuysngcui0xau.png)

Now let's configure load balancing for our Nginx. First, inside the nginx folder, create `nginx.conf`
```bash
~ touch nginx.conf
```
Let's edit the ´nginx.conf' and add these lines:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/e1uuw0vjx46n5gh5h51c.png)

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](https://aws.amazon.com/what-is/load-balancing/?nc1=h_ls).

**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](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`:
```Dockerfile
COPY ./nginx.conf /etc/nginx/nginx.conf
```
Your `Dockerfile` should look like this:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/r7qf8v68zuph258csoy5.png)

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:
```yaml
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
```

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/](http://myserver.local/) you will need to edit your `/etc/hosts` file.
```bash
~ sudo vim /etc/hosts
```

At the end of the file, add this line:
```bash
127.0.0.1 myserver.local
```
Now save the file and try to exit from vim 🤣

In your terminal run:
```bash
~ docker-compose up
```

This command will start the apps and Nginx. Now you can access the route at [http://myserver.local/](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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ggds4qwkrne8fsqsha42.png)

#### 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:
```yaml
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:
```

You can stop the containers using this command and start again:
```bash
~ docker-compose down // stop the containers
~ docker-compose up
```

To view your database, you can install [dbeaver](https://dbeaver.io/download/) I have been using DBeaver for a long time (thanks [bop](https://x.com/original_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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/c1jtybfe3sa1nr4hf2qp.png)

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](https://github.com/abraaoduarte/docker-node-nginx-sample)


<small>SDG</small>