Host multiple subdomains/applications on a single host using Docker

Docker - host multiple subdomains

Docker becomes more and more suitable for personal environments, especially with private servers, which can be migrated very often.

A developer usually have more than one app living on his own private server such as a blog, some development apps like Jenkins, GitLab and so on. These apps are likely to be using the standard web port 80. As this port is already bound to your main site for example, your Docker instances will not be accessible throughout this one.

This post will show you one way to host multiples applications such as a blog, a personal website and many others, on a single host using Docker containers.

Target architecture

The ideal architecture for hosting multiples apps within a dedicated server would be to expose each application on port 80 through a specific sub-domain (blog.domain.com, jenkins.domain.com, gitlab.domain.com).

Using Nginx as a reverse-proxy

These requirements can be achieved using a proxy (also called reverse-proxy). Here is a diagram:

Docker - host multiple subdomains - Nginx version

This classic architecture could be implemented using Nginx as a reverse-proxy, but this solution comes with inconvenients:

  • necessity to write a configuration file per application/container to proxy
  • reload Nginx each time an application or a container is added to the architecture.

Using nginx-proxy from Jason Wilder

Nginx-proxy consists in a simple Nginx server and docker-gen. Docker-gen is a small tool written in Go which can be used to generate Nginx/HAProxy configuration files using Docker containers meta-data (obtained via the Docker API).

These two applications are running as a Docker container and so are easy to get up running. Once started, nginx-proxy will act as a reverse proxy between your host and all your sub-domains (blog.domain.com, jenkins.domain.com, etc.), effectively routing incoming requests using the VIRTUAL_HOST environment variable (if set, for each Docker containers).

To proxy a Docker container, you basically have to expose the port the applications uses (for example 80 for WordPress) and add the VIRTUAL_HOST environment variable to the container:

Using docker run command:
docker run -d --expose 80 -e VIRTUAL_HOST=blog.domain.com wordpress

Via docker-compose.yml file:

wordpress:
  image: wordpress
  links:
    - db:mysql
  expose:
    - 80
  environment:
    - "VIRTUAL_HOST=blog.domain.com"
db:
  image: mariadb
  environment:
    MYSQL_ROOT_PASSWORD: example

The following configuration could be represented like this:

Docker - host multiple subdomains
As you can see above, the Nginx-proxy listens on http standard port (80) and forward incoming requests to the appropriate container. We will see later how this routing is made.

Starting Nginx-proxy

To start the nginx-proxy, type the following command:

shell docker run -d -p 80:80 -v /var/run/docker.sock:/tmp/docker.sock jwilder/nginx-proxy

Using docker-compose syntax:

nginx-proxy:
  image: jwilder/nginx-proxy
  ports:
    - "80:80"
  volumes:
    - /var/run/docker.sock:/tmp/docker.sock

Update:
As Moon suggested in his comment, you could add a piece of extra security to hide Nginx server version using a custom configuration file:

server_tokens off;

To make nginx-proxy use your custom Nginx config file, launch it with this flag:

-v /path/to/my_proxy.conf:/etc/nginx/conf.d/my_proxy.conf:ro

How it works ?

As you can guess with the last command, the nginx-proxy container listens on port 80 and has access to the host Docker socket. By giving the Docker host socket, the nginx-proxy container will be able to receive Docker events (ie. container creations, shutdowns, etc.), and react to them.

At its startup, the nginx-proxy container will look for containers with the VIRTUAL_HOST environment variable set and create appropriate basic Nginx configuration file for each of them. These configuration files will tell Nginx how to forward incoming requests to the underlying containers.

Then, each time a container starts, the nginx-proxy will receive an event and create an appropriate Nginx configuration needed to serve the container application and reload Nginx.

Routing requests using VIRTUAL_HOST environment variable:

Nginx-proxy will route requests to containers according to the VIRTUAL_HOST environment variable of each container. This means that if you want a container to be served with a specific domain or subdomain, you have to launch this one with the desired VIRTUAL_HOST environment variable.

Here is an example:

# Launch WordPress (db part omitted for clarity)
docker run -d --name blog --expose 80 -e VIRTUAL_HOST=blog.domain.com wordpress
# Launch Jenkins
docker run -d --name jenkinsci --expose 8080 -e VIRTUAL_HOST=jenkins.domain.com -e VIRTUAL_PORT=8080 jenkins

Again, here is the equivalent configuration for the Jenkins instance, using docker-compose syntax:

jenkins:
  image: jenkins
  expose:
    - 8080
    - 50000
  environment:
    - "VIRTUAL_HOST=jenkins.domain.com"
    - "VIRTUAL_PORT=8080"
  volumes:
    - "/your/home:/var/jenkins_home"

Note: the port used by the application inside the container must be exposed for nginx-proxy to see it. If the application exposes multiple ports, you have to tell nginx-proxy which port to proxy using the VIRTUAL_PORT environment variable.

In this example, nginx-proxy will forward all requests matching with blog.domain.com url to the WordPress container.
However, all requests beginning by the url jenkins.domain.com will be forwarded to the Jenkins container.

This tool is really simple and gives great flexibility. It allows running multiple Docker containers in the same dedicated server, without writing much configuration.

Tip:
Map a container to multiple domains:
A common requirement is using multiple domains for a given container. To do this, simply add hosts to VIRTUAL_HOST variable like this:
VIRTUAL_HOST=domain.com,www.domain.com,home.domain.com

Further documentation can be found at the following url: https://github.com/jwilder/nginx-proxy.