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 host
  • LETSENCRYPT_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/) […]

Leave a Reply

Avatar placeholder

Your email address will not be published. Required fields are marked *