Question

How to Host Multiple Docker Containers on a Single Droplet with Nginx Reverse Proxy?

Posted January 6, 2020 16.4k views
Linux BasicsDockerLinux Commands

Hi all!

Recently I had to setup a few small Docker containers for a couple of small websites.

As the sites were really small I didn’t want to run each one on a separate Droplet, so instead, I used Nginx with separate Nginx server blocks for each site and a reverse proxy for each Docker container.

Here’s how I set that up:

These answers are provided by our Community. If you find them useful, show some love by clicking the heart. If you run into issues leave a comment, or add your own answer to help others.

×
2 answers

Prerequisites

Before you start, make sure to have Docker and Nginx installed, here’s how to do that:

  • To install Docker follow the steps here:

https://www.digitalocean.com/community/questions/how-to-install-and-run-docker-on-digitalocean-dorplet

  • To install Nginx follow the steps here:

https://www.digitalocean.com/community/tutorials/how-to-install-nginx-on-ubuntu-18-04

Once you have both installed, you can continue with the steps:

Step 1 - run your Docker containers

For the same of simplicity, I will run a simple and I’ll run 2 small httpd containers.

  • Run your first container and map port 8080 on your host:
docker run -dit --name container-1 -p 8080:80 httpd:2.4

Now if you visit http://your-dropets-ip:8080, you should be able to see a message saying It Works!.

Just so that we could differentiate the two containers, let’s update the It works! message with Container 1 for example:

  • First get your container ID
docker ps

Then run the following sed command to update the message:

docker exec CONTAINER_ID sed -i 's/It works!/Container 1/' /usr/local/apache2/htdocs/index.html

This would basically run a search and replace for the It works! string and update it with Container 1 in the default index.html file in the container itself.

If you visit your Droplet’s IP again in your browser the message should change from It works! to Container 1.

Let’s do the same thing for container 2, but map it to port 8081 instead:

docker run -dit --name container-2 -p 8081:80 httpd:2.4

Then agian get your container ID

docker ps

Then run the sed command again to update the It works! message to Container 2:

docker exec CONTAINER_ID sed -i 's/It works!/Container 2/' /usr/local/apache2/htdocs/index.html

Now if you visit http://your-dropets-ip:8081, you should be able to see a message saying Container 2.

Step 2 - Configure Nginx

Now that we have our containers up and running we can go ahead and configure our Nginx server blocks, I will go ahead and use the following two subdomain names for this example:

  • container1.bobbyiliev.com
  • container2.bobbyiliev.com

To keep things as simple as possible, I will create 2 server blocks with the following content:

  • Server block #1:

Create a new file called container1.bobbyiliev.com.conf in the /etc/nginx/sites-available/ directory and add the following content:

server {
  listen        80;
  server_name   container1.bobbyiliev.com;

  location / {
    proxy_pass  http://localhost:8080;
  }
}
  • Server block #2:

Create a new file called container2.bobbyiliev.com.conf in the /etc/nginx/sites-available/ directory and add the following content:

server {
  listen        80;
  server_name   container2.bobbyiliev.com;

  location / {
    proxy_pass  http://localhost:8081;
  }
}

Then once you have the two config files ready cd to the /etc/nginx/sites-enabled directory, and run the following commands:

ln -s ../sites-available/container1.bobbyiliev.com.conf .

ln -s ../sites-available/container2.bobbyiliev.com.conf .

Run a config test to make sure that there are no errors:

nginx -t

And if you get Syntax OK message, restart Nginx:

systemctl restart nginx

Note, for more information about Nginx server blocks, I would recommend taking a look at this tutorial here:

https://www.digitalocean.com/community/tutorials/how-to-set-up-nginx-server-blocks-virtual-hosts-on-ubuntu-16-04

Step 3 - Test the setup

That is pretty much it, now if I visit container1.bobbyiliev.com I should be able to see the Container 1 message and the same for container2.bobbyiliev.com.

To test that I could run a simple curl request:

  • curl container1.bobbyiliev.com

You should see the following output

<h1>Container 1</h1>

Then run the same request for container2.bobbyiliev.com:

  • curl container2.bobbyiliev.com

And agian you should see the following output

<h1>Container 2</h1>

Video Demo

Here’s a quick video demo on how to do the above:

Conclusion

Now you have 2 different containers on the same Droplet being served from different domain names! Of course, this is just a very basic example, you could go a lot further by expanding your Nginx config a lot more, for example adding more headers to your Nginx proxy pass and even installing a Let’s Encrypt SSL.

Hope that this helps! Let me know if you have any questions!
Regards,
Bobby

by Justin Ellingwood
When using the Nginx web server, server blocks (similar to the virtual hosts in Apache) can be used to encapsulate configuration details and host more than one domain off of a single server. In this guide, we'll discuss how to configure server blocks in Nginx on an Ubuntu...
  • Thank you for your tutorial Bobby.

    I hope that you can help me out here, perhaps it is docker-compose.yml that is giving me these issues.
    I have started up your 2 containers as in (1) and (2)

    1. docker run -dit –name container-1 -p 8080:80 httpd:2.4
    2. docker run -dit –name container-2 -p 8081:80 httpd:2.4

    These 2 containers works as expected <p>

    Then I would like to run the nginx in a docker-compose.yml-file

    Something like this:

    version: '3.7'
    
    
    services:
    
      proxy:
        image: jwilder/nginx-proxy:0.7.0
        container_name: newproxy
        ports:
          - "80:80"
          - "443:443"
        volumes:
          - /var/run/docker.sock:/tmp/docker.sock:ro
          - ./nginx-proxy.conf:/etc/nginx/conf.d/nginx-proxy.conf:ro
    

    Where the nginx-proxy.conf has the following rows:

    server {
        listen          80;
        server_name     container1.bobbyiliev.com;
        location / {
            proxy_pass http://localhost:8080;
        }
    }
    
    server {
      listen        80;
      server_name   container2.bobbyiliev.com;
    
      location / {
        proxy_pass  http://localhost:8081;
      }
    }
    

    <p>
    I have updated /etc/hosts : mapping 127.0.0.1 to container1.bobbyiliev.com and container2.bobbyiliev.com

    <p> Logging into the nginx, I can verify that the nginx-proxy.conf-file has been mapped correctly.

    <p> I start up the nginx with the

    docker-compose up
    

    then I do the following curl-call

    1. curl container1.bobbyiliev.com

    the log from the docker-compose gives me

    newproxy | nginx.1    | 2020/04/02 21:55:46 [error] 70#70: *7 connect() failed (111: Connection refused) while connecting to upstream, client: 192.168.240.1, server: container1.bobbyiliev.com, request: "GET / HTTP/1.1", upstream: "http://127.0.0.1:8080/", host: "container1.bobbyiliev.com"
    

    Did google on ‘upstream’ and nginx and docker … <p>

    Best, Inki

    docker -v                      
    Docker version 19.03.8, build afacb8b7f0
    
    docker-compose -v                              
    docker-compose version 1.23.2, build 1110ad01
    
    • Hi there @Inkimar,

      This is an interesting case!

      I am fairly certain that your problem is caused by the localhost definition in your Nginx container config file. Basically localhost points to the Nginx container itself and not the Docker host. In the example, I’ve shown, I’m running Nginx directly on Docker Host, that is why it works localhost.

      What you could do is the following:

      • Create a Docker network and attach all 3 containers to that network. You can follow the steps on how to do that here:

      https://www.digitalocean.com/community/questions/how-to-ping-docker-container-from-another-container-by-name

      • After you’ve done this your containers will be able to communicate via their names. So what you could do is rather than setting localhost in your proxy pass rule you could just add the container names. In your case this would look like this:
      server {
          listen          80;
          server_name     container1.bobbyiliev.com;
          location / {
              proxy_pass http://container-1;
          }
      }
      
      server {
        listen        80;
        server_name   container2.bobbyiliev.com;
      
        location / {
          proxy_pass  http://container-2;
        }
      }
      

      Let me know how it goes!
      Regards,
      Bobby

Hi @bobbyiliev !

Thank you for your swift reply, this sorted things out.
I created a ’ docker-compose.yml-file for all 3 containers, kept the nginx-proxy.conf :

version: '3.7'

services:

  proxy:
    image: jwilder/nginx-proxy:0.7.0
    container_name: proxy-test
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - /var/run/docker.sock:/tmp/docker.sock:ro
      - ./nginx-proxy.conf:/etc/nginx/conf.d/nginx-proxy.conf:ro

  container1:
    image: httpd:2.4
    container_name: container-1
    environment:
      VIRTUAL_HOST: container1.bobbyiliev.com
    ports:
      - 8080:80

  container2:
    image: httpd:2.4
    container_name: container-2
    environment:
      VIRTUAL_HOST: container2.bobbyiliev.com
    ports:
      - 8081:80

And nginx-proxy.conf according to your instructions

server {
    listen          80;
    server_name     container1.bobbyiliev.com;
    location / {
        proxy_pass http://container-1;
    }
}

server {
    listen        80;
    server_name   container2.bobbyiliev.com;

    location / {
      proxy_pass  http://container-2;
    }
}
  1. docker exec container-1 sed -i ’s/It works!/Container 1/’ /usr/local/apache2/htdocs/index.html
  2. docker exec container-2 sed -i ’s/It works!/Container 2/’ /usr/local/apache2/htdocs/index.html
> curl localhost:8081
<html><body><h1>Container 2</h1></body></html>
> curl localhost:8080
<html><body><h1>Container 1</h1></body></html>

> curl container1.bobbyiliev.com
<html><body><h1>Container 1</h1></body></html>
> curl container2.bobbyiliev.com
<html><body><h1>Container 2</h1></body></html>

Thanks for that clarification, it really helped!

Another way that works the same way ?
Still, if I leave the out the nginx-proxy.conf-file then
the following attributes ‘VIRTUAL_HOST’ and 'ports’ in the docker-compose.yml-file will map the domain correctly. <p>
What is the benefits of using an nginx-proxy.conf in that case ?

And this , follow-up question goes a bit beyond your post, that would be putting an Matomo-ID (wrapped in a javascript) in the nginx-configuration file instead of cluttering up my different services with the Matomo-ID

Best, Inkimar

matomo

  • Hi there @Inkimar,

    No problem at all! I’m happy to hear that it is working now.

    The benefit of using jwilder/nginx-proxy is that you could have a single docker-compose file where you define all of your configurations and then if you need to deploy that to a new server, you simply need to run docker-compose and it would configure everything for you. Whereas with the approach that I’ve shown in the video, you would have to manually configure your Nginx instance each time you have to deploy on a new server.

    Regarding Matamo, I’ve not used it personally but it looks like a cool project. I’ll definitely check it out and share my thoughts!

    Regards,
    Bobby

Submit an Answer