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 10 Flat-File Content Managers to Help You Ditch WordPresssome other flat-file CMS. But, containerizing software leads to another problem that confuses many: 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.
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.
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.
To get started, create a Docker network
Before we get started, either way, we need to first create a Docker network that we will use to bridge all of these containers together.
$ docker network create nginx-proxy
From now on, we need to ensure that we're always adding new containers to the 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 titled 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 works to host multiple websites
As you can see from the code in both options, the container listens on port 80 and exposes the same port inside of the container. That allows all incoming traffic to flow though nginx
.
You might be wondering 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.
This means that every time you add a container, 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. nginx-proxy
looks for containers with the VIRTUAL_HOST
variable enabled, so that's critical to our operations moving forward.
Also important to note is the --net nginx-proxy
line in the Docker command, and the networks: default: external: name: nginx-proxy
block in the docker-compose.yml
file. These establish that all containers will communicate over that Docker network.
Step 2. Adding a container to the 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 information and direct traffic arriving to the correct domain.
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
.
Also, take note of the line that begins with db_node_domain
. If you are going to host multiple WordPress blogs using this method, you need to create unique database names for each. You should replace db_node_domain
with your preferring naming scheme. You also need to update two other fields with this chosen name. First:
depends_on:
- db_node_domain
And second:
WORDPRESS_DB_HOST: db_node_domain:3306
Once the configuration file is set up, you can run the docker-compose up -d
command. As long as your DNS is set up to route traffic properly, things should work correctly.
From here, you can start up any number of additional WordPress site—or any type of service, for that matter—and have them be automatically added to the nginx-proxy
network. This Docker and Nginx configuration is pretty infinitely extensible, limited only by the VPS resources available to you.
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!
A note about tutorials: We encourage our users to try out tutorials, but they aren't fully supported by our team—we can't always provide support when things go wrong. Be sure to check which OS and version it was tested with before you proceed.
If you want a fully managed experience, with dedicated support for any application you might want to run, contact us for more information.