Serving multiple web apps using HAProxy and Docker containers

Denis-Florin Rendler
webthoughts.koderhut.eu
5 min readNov 20, 2017

--

Disclaimer: The flow presented in this article can be used in a production environment, but IT DOES NOT address any security measures that will normally be needed to run it in a production environment.

Since I first started working with Docker, around 2 years ago, I found it very useful because of its speed booting up a complete development environment. It also only takes a few seconds to switch between projects which convinced me to use it as a default environment for working.

Nevertheless, one thing that I found bothering me was that I had to shutdown a container mapping a host specific port when I needed another container using that same port. The most common example is a web-server container. I always needed to stop the Nginx container for one project whenever I switched to a different project. I know that with Docker’ speed this only requires a couple of commands. But it’s still a couple of clicks that get annoying fast when I need to do this a few times during the course of a day.

So I started looking around for a solution. And this is what I came up with.

Initial Environment Configuration

I started looking at Nginx to be used as a proxy, but it requires a lot of configuration to get started with a new project. Next on the list was HAProxy. Its configuration is quite clean and easy to understand. It also, only requires a couple of lines to route a new project.

Below you can find the steps to get you started with this setup.

First we need to create the HAProxy container. We do so by running:

$ docker run -d -v /etc/haproxy:/usr/local/etc/haproxy:ro -p 80:80 \    
-p 443:443 --name web-gateway haproxy

This command will pull the latest HAProxy image and create a container from it. With this command Docker will also map a host folder, where we will keep the HAProxy configuration, and the HTTP(S) ports to the container. Bind mount-ing the configuration from the host will allow us to edit the configuration from the host and the container will just reload it.

Now that we configured the entry point to our server it is time to configure the web applications that we want to serve.

Note: for this article I will use two simple Nginx basic setups, but the exact same steps can be applied to any Docker-ized network application.

Next we will create three containers: one serving as a default server and two simulating serving two different apps.

$ docker run -d -v /var/www/default:/usr/share/nginx/html:ro \
--name default-backend nginx
$ docker run -d -v /var/www/web1:/usr/share/nginx/html:ro \
--name default-backend nginx
$ docker run -d -v /var/www/web2:/usr/share/nginx/html:ro \
--name default-backend nginx

Probably the first question you might have right now is how will the three new containers communicate with the HAProxy container since we didn’t map any host port? The answer is very simple: we will use Docker’s container networking.

Depending on how you would like to connect the apps you may choose to create separate networks for each container or reuse a network for several containers. In the later case, the apps will be aware of each other and accessible through the container’ name or IP.

For the purpose of this article I will create a separate network for each container so that we will keep the encapsulation features provided by Docker.

Let’s create the default server network:

$ docker network create —-attachable -d bridge default-webapp

We are using the ‘bridge’ driver because we will be using only the current machine. If you have a multiple machine setup you can use the ‘overlay’ driver. For more info, check-out the Docker documentation page.
The ‘attachable’ option will allow us to manually attach the network to our containers, which we will need to do later.
Finally, we provide a name for the network through the last parameter of the command.

Using the same command go ahead and create the networks for the other 2 containers using the ‘web1’ and ‘web2’ names.

Now that we configured the application containers, the HAProxy container as well as the networks let’s glue them together.

Run the following commands to connect the HAProxy container to the default Nginx server:

$ docker network connect default-webapp web-gateway 
$ docker network connect default-webapp default-backend

Now the two containers are accessible from each container using the container name. This gives us a flexible environment to work with because we no longer need to keep track of IPs and we can swap containers without changing any configuration. If, for example, you want to test your app with Apache or httpd just remove the Nginx container and create the new one using the same container name. Attach it to the network and you are done. The HAProxy will not need any other configuration change to proxy the connection to your new container.

Do the same for the other two containers by using the above two commands, but be careful to use the proper network and container names :)

Now that we connected the containers together it is time to configure HAProxy.

On the host create a file called `haproxy.cfg` under `/etc/haproxy` and paste the following configuration:

The sections that we will be focusing are the ‘frontend’ and ‘backend’.
The ‘frontend’ section declares a mapping of IP and port where HAProxy will listen to for connections. In our config we are configuring HAProxy to listen on all our IPv4 addresses using port 80 and 443.

The ‘default-backend’ option configures a default backend server where a request will be sent in case there are no other ACL’s matching.

The ACL’s (Access Control List) are HAProxy’s way for identifying where a request should be routed to in order to get a response. We will need to create a new ACL entry for each new application/domain we want to serve.

The ‘backend’ sections configure the servers that should answer to a request. Here, we can add as many containers we would like to respond to a request to our application.

As we can see, because we are using named Docker containers we can simply use the container name to route our request from the HAProxy to the application container.

Now restart the HAProxy container by running:

$ docker restart web-gateway

That’s it! Now, whenever we start a new project, we simply add a new network, connect HAProxy to it and add a new ACL line and backend server configuration.

Final thoughts

Using named Docker containers and container networking we can use a more descriptive and reusable HAProxy configuration. By using FQDN for containers we can have the same HAProxy configuration moved to a network where we use bare-bone servers and/or VMs instead of Docker containers. We can also use such servers along side containers allowing us to have quite a flexible environment.

Although this article focuses on a development environment this setup can be used in a production setup as well, as long as all the necessary security measures are addressed.

--

--