How to Deploy NGINX Reverse Proxy in Docker on Ubuntu 20.04

Proxy is very useful in a development environment when you need to expose several ports to access different modules of the application. You can use Reverse Proxy to access different modules of the application through the same URL. It will also help you to access the backend, frontend, and other services using a single domain name.

In this post, we will set up two websites inside two Docker containers, then set up an Nginx reverse proxy to access both websites.

Prerequisites

  • A fresh Ubuntu 20.04 server on the Atlantic.Net Cloud Platform
  • A root password configured on your server

For the purposes of this tutorial, we will use the following directory structure:

├── proxy
│   ├── backend-not-found.html
│   ├── default.conf
│   ├── docker-compose.yml
│   ├── Dockerfile
│   ├── includes
│   │   ├── proxy.conf
│   │   └── ssl.conf
│   └── ssl
│       ├── website1.crt
│       ├── website1.key
│       ├── website2.crt
│       └── website2.key
├── website1
│   ├── docker-compose.yml
│   └── index.html
└── website2
    ├── docker-compose.yml
    └── index.html

Step 1 – Create Atlantic.Net Cloud Server

First, log in to your Atlantic.Net Cloud Server. Create a new server, choosing Ubuntu 20.04 as the operating system with at least 4GB RAM. Connect to your Cloud Server via SSH and log in using the credentials highlighted at the top of the page.

Once you are logged in to your Ubuntu 20.04 server, run the following command to update your base system with the latest available packages.

apt-get update -y

Step 2 – Set Up a Host File

First, you will need to edit the /etc/hosts file and bind both website domains with the IP address:

nano /etc/hosts

Add the following lines:

your-server-ip   website1.test
your-server-ip   website2.test

Save and close the file when you are finished.

Step 3 – Install Docker and Docker Compose

First, you will need to install some required dependencies on your server. You can install them with the following command:

apt-get install git apt-transport-https ca-certificates curl gnupg-agent software-properties-common -y

After installing all of them, add the Docker repository with the following command:

curl -fsSL https://download.docker.com/linux/ubuntu/gpg | apt-key add -
add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"

Once the repository is added, install Docker and Docker Compose with the following command:

apt-get install docker-ce docker-compose -y

After installing both packages, you can proceed to the next step.

Step 4 – Create a First Website Container

First, create a directory to host your first website container.

mkdir website1

Next, change the directory to website1 and create a docker-compose.yml file:

cd website1
nano docker-compose.yml

Add the following lines:

version: '2'
services:
 app:
  image: nginx
  volumes:
   - .:/usr/share/nginx/html
  ports:
   - "80"

Save and close the file when you are finished.

Next, create an Index page for your website.

nano index.html

Add the following lines:

<!DOCTYPE html>
<html>
<head>
<title>First Website</title>
</head>
<body>
<h1>Hello! This is my first website.</h1>
</body>
</html>

Save and close the file then launch your website1 container with the following command:

docker-compose build
docker-compose up -d

This will download an Nginx image, launch a container, and expose it on port 80.

Creating network "website1_default" with the default driver
Pulling app (nginx:)...
latest: Pulling from library/nginx
69692152171a: Already exists
30afc0b18f67: Pull complete
596b1d696923: Pull complete
febe5bd23e98: Pull complete
8283eee92e2f: Pull complete
351ad75a6cfa: Pull complete
Digest: sha256:6d75c99af15565a301e48297fa2d121e15d80ad526f8369c526324f0f7ccb750
Status: Downloaded newer image for nginx:latest
Creating website1_app_1 ... done

Step 5 – Create a Second Website Container

First, create a directory to host your second website container.

cd
mkdir website2

Next, change the directory to website2 and create a docker-compose.yml file:

cd website2
nano docker-compose.yml

Add the following lines:

version: '2'
services:
 app:
  image: nginx
  volumes:
   - .:/usr/share/nginx/html
  ports:
   - "80"

Save and close the file, then create an Index page for the second website.

nano index.html

Add the following lines:

<!DOCTYPE html>
<html>
<head>
<title>Second Website</title>
</head>
<body>
<h1>Hello! This is my second website.</h1>
</body>
</html>

Save and close the file, then launch your website2 container with the following command:

docker-compose build
docker-compose up -d

You should see the following output:

Creating network "website2_default" with the default driver
Creating website2_app_1 ... done

You can now verify both website containers with the following command:

docker ps

You should get the following output:

CONTAINER ID   IMAGE     COMMAND                  CREATED              STATUS              PORTS                                     NAMES
01e83ba3a3dd   nginx     "/docker-entrypoint.…"   About a minute ago   Up About a minute   0.0.0.0:49154->80/tcp, :::49154->80/tcp   website2_app_1
75207f53411c   nginx     "/docker-entrypoint.…"   2 minutes ago        Up 2 minutes        0.0.0.0:49153->80/tcp, :::49153->80/tcp   website1_app_1

Step 6 – Set up Reverse Proxy Container

First, create a new proxy directory with the following command:

cd
mkdir proxy

Next, change the directory to proxy and create a Dockerfile for a new custom image:

cd proxy
nano Dockerfile

Add the following lines:

FROM nginx
COPY ./default.conf /etc/nginx/conf.d/default.conf
COPY ./backend-not-found.html /var/www/html/backend-not-found.html
COPY ./includes/ /etc/nginx/includes/
COPY ./ssl/ /etc/ssl/certs/nginx/

Save and close the file, then create a backend index file for a not found response.

nano backend-not-found.html

Add the following lines:

<html>
<head><title>Proxy Backend Not Found</title></head>
<body>
<h2>Proxy Backend Not Found</h2>
</body>
</html>

Save and close the file, then create an Nginx default configuration file:

nano default.conf

Add the following lines:

server {
listen 80;
listen 443 ssl http2;
server_name website1.test;

# Path for SSL config/key/certificate
ssl_certificate /etc/ssl/certs/nginx/website1.crt;
ssl_certificate_key /etc/ssl/certs/nginx/website1.key;
include /etc/nginx/includes/ssl.conf;

location / {
include /etc/nginx/includes/proxy.conf;
proxy_pass http://website1_app_1;
}

access_log off;
error_log /var/log/nginx/error.log error;
}

# web service2 config.
server {
listen 80;
listen 443 ssl http2;
server_name website2.test;

# Path for SSL config/key/certificate
ssl_certificate /etc/ssl/certs/nginx/website2.crt;
ssl_certificate_key /etc/ssl/certs/nginx/website2.key;
include /etc/nginx/includes/ssl.conf;

location / {
include /etc/nginx/includes/proxy.conf;
proxy_pass http://website2_app_1;
}

access_log off;
error_log /var/log/nginx/error.log error;
}

# Default
server {
listen 80 default_server;

server_name _;
root /var/www/html;

charset UTF-8;

error_page 404 /backend-not-found.html;
location = /backend-not-found.html {
allow all;
}
location / {
return 404;
}

access_log off;
log_not_found off;
error_log /var/log/nginx/error.log error;
}

Save and close the file when you are finished.

Next, create a docker-compose.yml file to launch a proxy container:

nano docker-compose.yml

Add the following lines:

version: '2'
services:
 proxy:
  build: ./
  networks:
   - website1
   - website2
  ports:
   - 80:80
   - 443:443

networks:
 website1:
  external:
   name: website1_default
 website2:
  external:
   name: website2_default

Save and close the file when you are finished.

Next, you will need to generate an SSL certificate and key for both websites.

First, create an SSL directory within your proxy directory:

mkdir ssl

Next, change the directory to ssl and generate ssl certificates and keys with the following command:

cd ssl
openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout website1.key -out website1.crt
openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout website2.key -out website2.crt

Next, go back to your proxy directory and create an includes directory:

cd ..
mkdir includes

Next, change the directory to includes and create a proxy configuration file:

cd includes
nano proxy.conf

Add the following lines:

proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_buffering off;
proxy_request_buffering off;
proxy_http_version 1.1;
proxy_intercept_errors on;

Save and close the file, then create an ssl.conf file:

nano ssl.conf

Add the following lines:

ssl_session_timeout 1d;
ssl_session_cache shared:SSL:50m;
ssl_session_tickets off;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers 'ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHAECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS';
ssl_prefer_server_ciphers on;

Save and close the file when you are finished.

Next, go back to the proxy directory and build the proxy container with the following command:

cd ..
docker-compose build

You should see the following output:

Building proxy
Step 1/5 : FROM nginx
 ---> d1a364dc548d
Step 2/5 : COPY ./default.conf /etc/nginx/conf.d/default.conf
 ---> d70a72d6011f
Step 3/5 : COPY ./backend-not-found.html /var/www/html/backend-not-found.html
 ---> 1cb5af66233f
Step 4/5 : COPY ./includes/ /etc/nginx/includes/
 ---> 7c18f2753b1e
Step 5/5 : COPY ./ssl/ /etc/ssl/certs/nginx/
 ---> ebb0447a1317
Successfully built ebb0447a1317
Successfully tagged proxy_proxy:latest

Next, launch the proxy container with the following command:

docker-compose up -d

Output:

Creating proxy_proxy_1 ... done

You can now verify your all containers with the following command:

docker ps -a

You should get the following output:

CONTAINER ID   IMAGE         COMMAND                  CREATED             STATUS                      PORTS                                                                      NAMES
c413da7e85ba   proxy_proxy   "/docker-entrypoint.…"   16 seconds ago      Up 15 seconds               0.0.0.0:80->80/tcp, :::80->80/tcp, 0.0.0.0:443->443/tcp, :::443->443/tcp   proxy_proxy_1
01e83ba3a3dd   nginx         "/docker-entrypoint.…"   52 minutes ago      Up 52 minutes               0.0.0.0:49154->80/tcp, :::49154->80/tcp                                    website2_app_1
75207f53411c   nginx         "/docker-entrypoint.…"   53 minutes ago      Up 53 minutes               0.0.0.0:49153->80/tcp, :::49153->80/tcp                                    website1_app_1

Step 7 – Check the Nginx Reverse Proxy

At this point, both the website and proxy container are started successfully. Now, it’s time to test it.

First, your first website using the following command:

curl website1.test

If everything is fine, you should see your first website page:

<!DOCTYPE html>
<html>
<head>
<title>First Website</title>
</head>
<body>
<h1>Hello! This is my first website.</h1>
</body>
</html>

Now, test your second website with the following command:

curl website2.test

If everything is fine, you should see your second website page:

<!DOCTYPE html>
<html>
<head>
<title>Second Website</title>
</head>
<body>
<h1>Hello! This is my second website.</h1>
</body>
</html>

Conclusion

In the above guide, you learned how to set up an Nginx reverse proxy container for two websites within a Docker container. This should help you to deploy an application in the development environment; try it on your dedicated server from Atlantic.Net.