Host multiple websites on one VPS with Docker and Nginx

How to use Docker and Nginx to host multiple websites

Docker is an excellent tool for running multiple services on a single VPS without them interfering with each other—for example, one website built on WordPress and another built on Ghost or some other flat-file CMS.

But, containerizing software leads to another problem: How do I host multiple websites, each in a separate Docker container, from one VPS? Fortunately, with a little bit of foresight and configuring, you can use Docker and Nginx to host multiple websites from a single VPS.

By default, Docker services all listen on port 80, which would create conflicts for incoming traffic. You can change the listening port, of course, but no one wants to type in coolwebsite.com:34567 to access their favorite site.

What if, instead, you could use nginx to route traffic arriving at coolwebsite.com to a unique to a container listening on the 34567 port, and route traffic arriving to anothercoolwebsite.net a second container listening to 45678?

That’s exactly what nginx-proxy does: it listens to port 80 (the standard HTTP port) and forwards incoming requests to the appropriate container. This is often known as a reverse proxy, and takes advantage of Docker’s VIRTUAL_HOST variable.

In this tutorial, we’ll set up nginx-proxy and learn how to use Docker and Nginx to route traffic to different containers, thereby allowing you to host multiple websites on different domains from a single website.

April 17, 2019: Updated with clearer instructions and updated examples!

Prerequisites

    • An Ubuntu/Debian/CentOS server with Docker installed—to get started with that, check out our getting started with Docker tutorial.
    • A non-root user with sudo privileges.
    • A fully-qualified domain name from a domain registrar.
    • The ability to change DNS settings via your domain registrar.

Get a Docker-ready VPS with 16GB RAM & SSD for $7.99/mo!

Host multiple sites affordably using Docker, Nginx…and SSD Nodes! Our Intel and SSD-backed servers and Tier-1 network connections give you the performance you need, for less. Deploy your new server today and save!

Step 1. Starting up nginx-proxy to hook Docker and Nginx together

To get started, let’s start up the nginx-proxy container. This can be accomplished either by a single docker command, or using docker-compose. Let’s cover both.

But before we do that, let’s create a Docker network. The network will allow our containers to “talk” among themselves.

$ docker network create nginx-proxy

From now on, we need to add new containers to this nginx-proxy Docker network.

Installing nginx-proxy with Docker

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

Installing nginx-proxy with docker-compose

First, create a new docker-compose.yml file in the directory of your choosing (one named nginx-proxy is a good idea), and copy in the following text:

version: "3"
services:
  nginx-proxy:
    image: jwilder/nginx-proxy
    container_name: nginx-proxy
    ports:
      - "80:80"
    volumes:
      - /var/run/docker.sock:/tmp/docker.sock:ro

networks:
  default:
    external:
      name: nginx-proxy

And then run the following docker-compose command to get started.

$ docker-compose up -d

How nginx-proxy uses Docker and Nginx to host multiple websites on one VPS

First, the nginx-proxy container listens on port 80. All traffic that’s incoming to the VPS on port 80 will pass through nginx-proxy. That’s a good first step. But how does it know whether to send person A to blog.example.com and person B to app.example.com?

That’s what the /var/run/docker.sock:/tmp/docker.sock line accomplishes. Essentially, this gives any container access to the host’s Docker socket, which contains information about a variety of Docker events, such as creating a new container, or shutting one down.

Every time you add a new Docker container to your network, nginx-proxy sees the event through the socket, automatically creates the configuration file needed to route traffic, and restarts nginx to make the changes available immediately.

The proxy looks for containers with the VIRTUAL_HOST variable enabled. In that variable you specify the destination, like blog.example.com or app.example.com. The proxy will see that person A has requested blog.example.com and will route (or proxy!) their request to the correct container. Same goes for app.example.com or any other containers you want to run.

Also take note of the --net nginx-proxy line in the Docker command, and the networks: default: external: name: nginx-proxy block in the docker-compose.yml file. That’s how you specify which Docker network you want the new container to use.

Step 2. Adding your first Docker container to the Nginx proxy

Now that we have nginx-proxy running, we can start adding new containers, which will be automatically picked up and configured for. Because we covered it in the last Docker tutorial, and since it’s an easy implementation to try out, let’s use WordPress as an example.

Using Docker

Starting a WordPress container with a basic configuration is quite easy.

$ docker run -d --name blog --expose 80 --net nginx-proxy -e VIRTUAL_HOST=blog.DOMAIN.TLD wordpress

Take note of a few elements here. --expose 80 will allow traffic to flow into the container on port 80. --net nginx-proxy ensures we’re using the Docker network we created earlier. Finally, -e VIRTUAL_HOST=blog.DOMAIN.TLD flags nginx-proxy to create the proxy and direct any traffic requesting that domain to this new Docker container. Replace DOMAIN.TLDwith the domain of your choice.

Using docker-compose

Start by creating a ‘docker-compose.yml’ file in an empty directory and copying in the following.

version: "3"

services:
   db_node_domain:
     image: mysql:5.7
     volumes:
       - db_data:/var/lib/mysql
     restart: always
     environment:
       MYSQL_ROOT_PASSWORD: PASSWORD
       MYSQL_DATABASE: wordpress
       MYSQL_USER: wordpress
       MYSQL_PASSWORD: PASSWORD
     container_name: wordpress_db

   wordpress:
     depends_on:
       - db_node_domain
     image: wordpress:latest
     expose:
       - 80
     restart: always
     environment:
       VIRTUAL_HOST: blog.DOMAIN.TLD
       WORDPRESS_DB_HOST: db_node_domain:3306
       WORDPRESS_DB_USER: wordpress
       WORDPRESS_DB_PASSWORD: PASSWORD
     container_name: wordpress
volumes:
    db_data:

networks:
  default:
    external:
      name: nginx-proxy

Again, take note of the expose and environment: VIRTUAL_HOST options within the file. Also, the networks option at the bottom is necessary to allow this container to “speak” with nginx-proxy.

You will also want to change passwords for the MySQL database itself, and for the WordPress user that accesses said database. WordPress will create a new password for your WordPress administrator account automatically during the set-up process.

Once the configuration file is finalized, you can run the docker-compose up -d command.

Step 3. Visiting your new WordPress site and adding more

At this point, you need to configure your domain’s DNS settings to point toward the IP address of your VPS. If you set up your blog on blog.DOMAIN.TLD, create an A record for that subdomain and point it to the same IP address.

With your DNS set up correctly, you should be able to visit blog.DOMAIN.TLD and see the famous 5-minute WordPress installation! Congratulations!

You can now boot up any other Docker containers, such as another WordPress site or any other type of app/service, and nginx-proxy will take care of the rest.

One caveat for multiple WordPress installations

If you are going to host multiple WordPress blogs using this method, you need to create a unique database name for each instance. By doing so you that WordPress A connects to MySQL A, WordPress B connects to MySQL B, and so on.

See the line just under services? The one that reads db_node_domain? That’s the name of your container. You can pick any naming scheme you’d like, but each instance needs to have a different name.

Once you’ve chosen a name, you need to update two other fields. First:

depends_on:

  - db_node_domain

And second:

WORDPRESS_DB_HOST: db_node_domain:3306

Additional resources to host multiple websites

Of course, be sure to check out the extensive documentation for nginx-proxy to learn more about how you can configure some more complex proxies between Docker and Nginx, such as those using SSL, with multiple ports, or multiple networks.

We haven’t tested it out yet, but there’s a “companion” to nginx-proxy called letsencrypt-nginx-proxy-companion that allows for the creation/renewal of Let’s Encrypt certificates automatically directly alongside the proxy itself.

Just another example of the really cool things that you can do with Docker!
Thanks much to our sharp-eyed reader John! He pointed out how we can improve our docker-compose files by creating unique database names for each instance of WordPress!

SPRING_VPS_SALE_16GB