In this tutorial, we will be setting up several containerized applications (websites) to run on a single server using an automated Nginx reverse proxy. Additionally, we will use a LetsEncrypt proxy companion to automatically provision the websites with Let’s Encrypt certificates.
Reverse Nginx Proxy
A Reverse Proxy is a service that is put in front of other web services to enable load balancing, request routing, and caching. Often it is used to allow multiple web applications to share the same host server’s ports (typically 80 and 443). This can also be done with Apache Virtual Hosts, but using an Nginx Reverse Proxy allows much better flexibility. For example, it solves the problem of running multiple websites as Docker containers on a single host.
Read more on the background of this idea here: Nginx Reverse Proxy for Docker.
Scenario Setup
We will create three projects: proxy, site1, and site2. The first will implement nginx-proxy and docker-letsencrypt-nginx-proxy-companion. The latter two will be WordPress blogs.
The proxy will enable the hosting of the two WordPress blogs and will provide them with Let’s Encrypt certificates.
For simplicity, we will pack all the projects in the same repository, but in a real-world scenario, they can be completely independent repositories.
The repository folder structure will be this:
├── repository_root
│ ├── proxy
│ │ ├──docker-compose.yml
│ ├── site1
│ │ ├──docker-compose.yml
│ ├── site2
│ │ ├──docker-compose.yml
Prerequisites
For this project, we will need a Linux-based server with Docker installed. You will also need at least 2 valid hostnames that resolve to the host server’s IP. In our example, we will be using site1.yourdomain.tld
and site2.yourdomain.tld
.
Network
We will need a network that the containerized web servers can use to communicate to the reverse proxy. On the host service, create a Docker network called nginx-proxy
:
docker network create nginx-proxy
Preparing the Proxy
We will follow the instructions available in the nginx-proxy project repositories nginx-proxy and docker-letsencrypt-nginx-proxy-companion.
In the proxy
folder we create a docker-compose.yml
file and define two services: nginx-proxy and letsencrypt-proxy.
The configuration of the first service is this:
version: "3"
services:
nginx-proxy:
image: jwilder/nginx-proxy
container_name: nginx-proxy
ports:
- "80:80"
- "443:443"
volumes:
- /var/run/docker.sock:/tmp/docker.sock:ro
- letsencrypt-certs:/etc/nginx/certs
- letsencrypt-vhost-d:/etc/nginx/vhost.d
- letsencrypt-html:/usr/share/nginx/html
Here, we will create a service based on the jwilder/nginx-proxy image and exposes ports 80 and 443.
Important here is the volume configuration. The /var/run/docker.sock
volume allows the proxy to react to Docker events on the host server and automatically modify the Nginx reverse proxy configuration. This way, we can add containerized websites to the host without manually reconfiguring the reverse proxy.
The other three binds are required by the LetsEncrypt companion proxy and will be shared between the two proxy containers.
The configuration of the LetsEncrypt companion proxy is this:
letsencrypt-proxy:
image: jrcs/letsencrypt-nginx-proxy-companion
container_name: letsencrypt-proxy
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- letsencrypt-certs:/etc/nginx/certs
- letsencrypt-vhost-d:/etc/nginx/vhost.d
- letsencrypt-html:/usr/share/nginx/html
environment:
- DEFAULT_EMAIL=some_email@yourdomain.tld
- NGINX_PROXY_CONTAINER=nginx-proxy
Here, we use the jrcs/letsencrypt-nginx-proxy-companion to create a service that will automatically request Let’s Encrypt certificates every time we add a new containerized website to the host.
For the automation to work, the container will have to listen to Docker events on the host, which is why it uses /var/run/docker.sock:/var/run/docker.sock:ro
volume.
It also shares volumes with the nginx-proxy service. This service’s container need to be identified by name via the environment variable NGINX_PROXY_CONTAINER
.
The DEFAULT_EMAIL
will be used by Let’s Encrypt to notify you about the certificate expiration.
The complete docker-compose.yml
file for the proxy project will have this content:
version: "3"
services:
nginx-proxy:
image: jwilder/nginx-proxy
container_name: nginx-proxy
ports:
- "80:80"
- "443:443"
volumes:
- /var/run/docker.sock:/tmp/docker.sock:ro
- letsencrypt-certs:/etc/nginx/certs
- letsencrypt-vhost-d:/etc/nginx/vhost.d
- letsencrypt-html:/usr/share/nginx/html
letsencrypt-proxy:
image: jrcs/letsencrypt-nginx-proxy-companion
container_name: letsencrypt-proxy
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- letsencrypt-certs:/etc/nginx/certs
- letsencrypt-vhost-d:/etc/nginx/vhost.d
- letsencrypt-html:/usr/share/nginx/html
environment:
- DEFAULT_EMAIL=some_email@yourdomain.tld
- NGINX_PROXY_CONTAINER=nginx-proxy
networks:
default:
external:
name: nginx-proxy
volumes:
letsencrypt-certs:
letsencrypt-vhost-d:
letsencrypt-html:
As you can see, we use the nginx-proxy
Docker network that we created earlier.
Preparing the Websites
Each of the two WordPress websites is implemented as a two-service project: a service for the MySQL server and a service for a WordPress web application.
The content of the site1
‘s docker-compose.yml
file is this:
version: "3"
services:
db_node_domain:
image: mysql:5.7
volumes:
- db_data:/var/lib/mysql
restart: unless-stopped
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: unless-stopped
environment:
VIRTUAL_HOST: site1.yourdomain.tld
LETSENCRYPT_HOST: site1.yourdomain.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
To make use of the Nginx reverse proxy and the LetsEncrypt proxy companion, we only need to provide two environment variables:
VIRTUAL_HOST
– will be used by the Nginx reverse proxy to autoconfigure the virtual hostLETSENCRYPT_HOST
– will be used by the LetsEncrypt proxy companion to request SSL certificates
Additionally, the network must be set to use the nginx-proxy
Docker network.
The other site’s configuration is the same. The only difference is the names of the containers and the hostnames:
File site2/docker-compose.yml
:
version: "3"
services:
db_node_domain_2:
image: mysql:5.7
volumes:
- db_data:/var/lib/mysql
restart: unless-stopped
environment:
MYSQL_ROOT_PASSWORD: password
MYSQL_DATABASE: wordpress
MYSQL_USER: wordpress
MYSQL_PASSWORD: password
container_name: wordpress_db_2
wordpress_2:
depends_on:
- db_node_domain_2
image: wordpress:latest
expose:
- 80
restart: unless-stopped
environment:
VIRTUAL_HOST: site2.yoursite.tld
LETSENCRYPT_HOST: site2.yoursite.com
WORDPRESS_DB_HOST: db_node_domain_2:3306
WORDPRESS_DB_USER: wordpress
WORDPRESS_DB_PASSWORD: password
container_name: wordpress_2
volumes:
db_data:
networks:
default:
external:
name: nginx-proxy
Test it Out
Pull the project code to the host server, change into the proxy
directory and run:
docker-compose up -d
Make sure that both services are running by executing:
docker-compose ps
Name Command State Ports
-----------------------------------------------------------------------------------------------------
letsencrypt-proxy /bin/bash /app/entrypoint. ... Up
nginx-proxy /app/docker-entrypoint.sh ... Up 0.0.0.0:443->443/tcp, 0.0.0.0:80->80/tcp
Start Websites
Switch to the first website directory site1
and run docker-compose up -d
Issuing the certificates will take a bit, so when running for the first time, allow about a minute before checking if the URL https://site1.yourdomain.tld
works.
Repeat the same for the other website.
Further Information
If you want to customize this setup, the projects nginx-proxy and docker-letsencrypt-nginx-proxy-companion provide quite detailed documentation that will answer most questions.
6 Comments
rykker · March 6, 2021 at 6:41 am
I am astounded! This worked like a charm. Thank you so much!
Deploy Containerized WordPress Websites with GitLab and Nginx-Proxy - Singular Aspect · June 3, 2020 at 1:01 pm
[…] We will need a remote Linux-based server with Docker and docker-compose installed. Additionally, you have to setup nginx-proxy and letsencrypt-proxy-companion as described in their documentation or in this post: Use Nginx-Proxy and LetsEncrypt Companion to Host Multiple Websites. […]
Words by DL – Deploy Containerized WordPress Websites with GitLab and Nginx-Proxy · June 4, 2020 at 5:02 pm
[…] We will need a remote Linux-based server with Docker and docker-compose installed. Additionally, you have to setup nginx-proxy and letsencrypt-proxy-companion as described in their documentation or in this post: Use Nginx-Proxy and LetsEncrypt Companion to Host Multiple Websites. […]
Multiple WordPress Websites in Docker with Let’s Encrypt and Nginx-Proxy in a single Raspberry Pi Server (or any server) – Marco Farias · July 19, 2021 at 2:28 am
[…] And also special thanks to Oleg Ishenko since this tutorial is mostly based on his tutorial Use Nginx-Proxy and LetsEncrypt Companion to Host Multiple Websites […]
Multiple WordPress Websites in Docker with Let’s Encrypt and Nginx-Proxy in a single Raspberry Pi Server (or any server) – Marco Farias · July 19, 2021 at 2:28 am
[…] And also special thanks to Oleg Ishenko since this tutorial is mostly based on his tutorial Use Nginx-Proxy and LetsEncrypt Companion to Host Multiple Websites […]
Can someone outside of the lan uses my nginx proxy? - Boot Panic · March 18, 2022 at 1:58 pm
[…] I have a vm running multiple docker containers. To simplify their use and because I need https we set up a nginx reverse proxy in a container jwilder/nginx-proxy (following this method https://www.singularaspect.com/use-nginx-proxy-and-letsencrypt-companion-to-host-multiple-websites/) […]