How to set up Nginx as a reverse proxy? How to achieve it with Docker and docker-compose? Today I will show you a simple setup example.

What do we want to achieve?

One picture says more than a thousand words. Take a look at the diagram below.

Our example architecture
Our example architecture

Our sample infrastructure will consist of:

  • Nginx configured as a reverse proxy,
  • Two Nginx instances as services behind proxy.

Configuration

To begin with, we define the structure of the files and directories that will be discussed:

.
├── docker-compose.yml      # infrastructure definition
├── nginx_configs           # proxy configuration files
│   ├── default.conf
│   ├── service_a.conf
│   └── service_b.conf
├── proxy                   # proxy HTML contents
│   └── index.html
├── service_a               # service_a HTML contents
│   └── index.html
└── service_b               # service_b HTML contents
    └── index.html

Infrastructure definition

To quickly set up our infrastructure, we will use the following docker-compose.yml file.

version: '3.9'

services:
  proxy:
    image: nginx:1-alpine
    volumes:
      - ./nginx_configs/:/etc/nginx/conf.d/:ro
      - ./proxy/:/usr/share/nginx/html/:ro
    ports:
      - "8080:80"
    networks:
      - internal-network
  service_a:
    image: nginx:1-alpine
    hostname: service_a
    volumes:
      - ./service_a/:/usr/share/nginx/html/:ro
    networks:
      - internal-network
  service_b:
    image: nginx:1-alpine
    hostname: service_b
    volumes:
      - ./service_b/:/usr/share/nginx/html/:ro
    networks:
      - internal-network

networks:
  internal-network:

Service A and service B have to be in the same network as proxy. In our case it is a network called internal-network.

I also gave both services a hostname. Internal Docker DNS resolver will point at our services by their hostnames.

We would like to expose our proxy to the world. To achieve it I exposed proxy HTTP port.

Proxy configuration

I mounted the directory with our proxy configuration files. Let’s configure a proxy so that it can route traffic to our services.

nginx_configs/service_a.conf contents:

upstream service_a {
    # Here we configure Nginx where to redirect the traffic
    server service_a:80;
}

server {
    # The port on which our proxy is listening 
    listen 80;
    # Domain for which traffic is to be redirected
    server_name service_a.example.com;

    # Forward Host header
    proxy_set_header Host $host;

    # We want all locations to point to service A
    location / {
        # service_a is our upstream
        proxy_pass http://service_a;
    }
}

nginx_configs/service_b.conf contents:

upstream service_b {
    server service_b:80;
}

server {
    listen 80;
    server_name service_b.example.com;

    proxy_set_header Host $host;

    location / {
        proxy_pass http://service_b;
    }

The following configuration allows the proxy to handle traffic going directly to it. If we do not define a default configuration, Nginx will direct traffic to the first service encountered in the configuration. We want to avoid this scenario.

nginx_configs/default.conf contents:

server {
    listen 80;

    location / {
        root    /usr/share/nginx/html;
        index   index.html index.htm;
    }
}

Services configuration

To distinguish the services from each other, we will replace their default index.html files. By default, each Nginx instance will respond with the same content, and we won’t be able to tell them apart when testing.

Create proxy/index.html file with following contents:

This is proxy service

Then service_a/index.html:

This is service A

And finally service_b/index.html:

This is service B

Testing

Now is the time to get our infrastructure up and running and test the proxy server. Run docker-compose and wait for all containers to start working.

$ docker-compose up -d

Then make the following requests to our proxy.

$ curl -s http://localhost:8080
This is proxy service
$ curl -s -H "Host: service_a.example.com" http://localhost:8080
This is service A
$ curl -s -H "Host: service_b.example.com" http://localhost:8080
This is service B

Seems to be working!

Summary

As you can see, setting up a simple reverse proxy is easy. It is worth mentioning that the proxy configured in this way is not properly secured (no SSL support). However, security is not the topic of this post.

I put the whole example into the GitHub repository. You can find it here.

If you have any questions or comments, please feel free to contact me.