Nodes
Tutorial: Extending Docker and Nginx to Host Multiple Websites with SSL

Tutorial: Extending Docker and Nginx to Host Multiple Websites with SSL

Posted by Joel Hans on Nov 22, 2017

In one of our most popular tutorials—Using Docker and Nginx to Host Multiple Websites—I covered how you can use the nginx-proxy Docker container to host multiple websites or web apps on a single VPS using different containers.

As I was looking to enable HTTPS on some of my self-hosted services recently, I thought it was about time to take that tutorial a step further and show you how to set it up to request Let’s Encrypt HTTPS certificates.

With the help of docker-letsencrypt-nginx-proxy-companion (Github), we’ll be able to have SSL automatically enabled on any new website or app we deploy with Docker containers.

Prerequisites

  • Any of our OS options—Ubuntu, Debian, or CentOS. Just a note: we’ve only tested Ubuntu 16.04 as of now.
  • A running Docker installation, plus docker-compose—see our Getting Started with Docker guide for more information.

Need a server to set up your own nginx-proxy? We're 4x faster than the competition, and only 1/4 of the price.

Start proxying

Step 1. Getting set up, and a quick note

I’m a rather big fan of using docker-compose whenever possible, as it seems to simplify troubleshooting containers that are giving you trouble. Instead of parsing a long terminal command, you can simply edit the docker-compose.yml file, re-run docker-compose up -d, and see the results.

As a result, this tutorial will be heavily biased toward using docker-compose over docker commands, particularly when it comes to setting up the docker-letsencrypt-nginx-proxy-companion service.

If you’re interested creating these containers via docker commands, check out the docker-letsencrypt-nginx-proxy-companion documentation.

Already have nginx-proxy set up via our previous tutorial? You can simply overwrite your existing docker-compose.yml file with the file you’ll find in Step 2.

If you’re just getting started with nginx-proxy, start here. You’ll want to start by creating a separate directory to contain your docker-compose.yml file.

$ mkdir nginx-proxy && cd nginx-proxy

Once that’s finished, issue the following command to create a unique network for nginx-proxy and other Docker containers to communicate through.

docker network create nginx-proxy

Step 2. Creating the docker-compose.yml file

In the nginx-proxy directory, create a new file named docker-compose.yml and paste in the following text:

version: '3'

services:
  nginx:
    image: nginx:1.13.1
    container_name: nginx-proxy
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - conf:/etc/nginx/conf.d
      - vhost:/etc/nginx/vhost.d
      - html:/usr/share/nginx/html
      - certs:/etc/nginx/certs
    labels:
      - "com.github.jrcs.letsencrypt_nginx_proxy_companion.nginx_proxy=true"

  dockergen:
    image: jwilder/docker-gen:0.7.3
    container_name: nginx-proxy-gen
    depends_on:
      - nginx
    command: -notify-sighup nginx-proxy -watch -wait 5s:30s /etc/docker-gen/templates/nginx.tmpl /etc/nginx/conf.d/default.conf
    volumes:
      - conf:/etc/nginx/conf.d
      - vhost:/etc/nginx/vhost.d
      - html:/usr/share/nginx/html
      - certs:/etc/nginx/certs
      - /var/run/docker.sock:/tmp/docker.sock:ro
      - ./nginx.tmpl:/etc/docker-gen/templates/nginx.tmpl:ro

  letsencrypt:
    image: jrcs/letsencrypt-nginx-proxy-companion
    container_name: nginx-proxy-le
    depends_on:
      - nginx
      - dockergen
    environment:
      NGINX_PROXY_CONTAINER: nginx-proxy
      NGINX_DOCKER_GEN_CONTAINER: nginx-proxy-gen
    volumes:
      - conf:/etc/nginx/conf.d
      - vhost:/etc/nginx/vhost.d
      - html:/usr/share/nginx/html
      - certs:/etc/nginx/certs
      - /var/run/docker.sock:/var/run/docker.sock:ro

volumes:
  conf:
  vhost:
  html:
  certs:

# Do not forget to 'docker network create nginx-proxy' before launch, and to add '--network nginx-proxy' to proxied containers. 

networks:
  default:
    external:
      name: nginx-proxy

Special thanks to Github user buchdag for this all-in-one configuration that works out of the box (most of the time).

After you copy, make sure that the formatting looks the same—the yml parser isn’t kind, let’s say, to syntax errors.

Now, take a closer look at line 30, which contains - ./nginx.tmpl:/etc/docker-gen/templates/nginx.tmpl:ro. This line creates a Docker volume between a file on your host filesystem (in this case, in the nginx-proxy directory) and a file inside one of the Docker containers. For that volume to work, we need to supply the nginx.tmpl file.

Inside of the nginx-proxy directory, use the following curl command to copy the developer’s sample nginx.tmpl file to your VPS.

$ curl https://raw.githubusercontent.com/jwilder/nginx-proxy/master/nginx.tmpl > nginx.tmpl

Step 3. Running nginx-proxy for the first time.

If you have an nginx-proxy container running already from the previous tutorial, you’ll need to stop it before moving forward.

You can now run the docker-compose command that will kick this all off.

$ docker-compose up -d

The process will start by downloading a few Docker images, and if things finish successfully, the output will end with the following:

Creating nginx-proxy ...
Creating nginx-proxy ... done
Creating nginx-proxy-gen ...
Creating nginx-proxy-gen ... done
Creating nginx-proxy-le ...
Creating nginx-proxy-le ... done

To confirm this, run docker ps. You should see three running containers, named nginx-proxy, nginx-proxy-gen, and nginx-proxy-le, like this:

CONTAINER ID        IMAGE                                    COMMAND                  CREATED             STATUS              PORTS                                      NAMES
9ea5fffc24dd        jrcs/letsencrypt-nginx-proxy-companion   "/bin/bash /app/en..."   4 minutes ago       Up 4 minutes                                                   nginx-proxy-le
e2288dfc3c5c        jwilder/docker-gen:0.7.3                 "/usr/local/bin/do..."   4 minutes ago       Up 3 seconds                                                   nginx-proxy-gen
eda8f12bd829        nginx:1.13.1                             "nginx -g 'daemon ..."   4 minutes ago       Up 4 minutes        0.0.0.0:80->80/tcp, 0.0.0.0:443->443/tcp   nginx-proxy

If any of those aren’t running, you should check their logs. You can do that with docker log.

$ docker log nginx-proxy
$ docker log nginx-proxy-gen
$ docker log nginx-proxy-le

I’ve found that most issues arise from nginx-proxy-gen. If you see an error about the nginx-proxy network, try creating the network again with docker network create nginx-proxy. If there are issues with the nginx.tmpl file, double check that it’s the same as the one on Github.


If your three containers are running smoothly, then you’re ready to start deploying other SSL-enabled containers behind the proxy! At this point, we’re shifting away from configuring nginx-proxy and toward the ways, you should configure your apps to run behind it.

Step 4. Set up your DNS

Let’s Encrypt will only issue certificates to real domains, not IPs. In order to receive valid certificates, you must set up your DNS correctly. Most likely, you will find your DNS settings with the company from which you bought your domain.

Let’s say I want to run Miniflux, a self-hosted RSS reader, on the feeds.example.com domain. In my DNS settings, I would create a new A record that directs feeds.example.com to the public IP of the VPS where I set up my nginx-proxy.

This is not an optional step. Valid, reachable domains are required for SSL to work.

Quick tip: to find your VPSs’ public IP, use the following: dig +short myip.opendns.com @resolver1.opendns.com.

Step 5. The configuration basics

In order to set up any containerized app to work with this beautiful proxy we’ve now set up, you must configure the following:

  • Three environment variables: VIRTUAL_HOST, LETSENCRYPT_HOST, LETSENCRYPT_EMAIL
  • The Docker network (nginx-proxy)
  • Exposing port 80/443

Environment variables

Here are the environment variables:

VIRTUAL_HOST
LETSENCRYPT_HOST
LETSENCRYPT_EMAIL

The VIRTUAL_HOST and LETSENCRYPT_HOST variables will be the same for almost all applications, and will correspond to the domain you used in the previous step to set up DNS.

The LETSENCRYPT_EMAIL variable is self-explanatory: use the email address of your choosing.

Let’s extend our example from the last step. Here’s the docker-compose settings I would use for that Miniflux installation, given that my email is [email protected]:

environment:
    VIRTUAL_HOST: feeds.example.com
    LETSENCRYPT_HOST: feeds.example.com
    LETSENCRYPT_EMAIL: [email protected]

Docker network

This will be nginx-proxy, unless you changed it. Here’s how it looks in a docker-compose.yml file:

networks:
  default:
    external:
      name: nginx-proxy

Expose port(s)

Any container you create must expose the port on which it listens to traffic. That will be 80, 443, or both. Here’s how it looks in a docker-compose.yml file:

expose:
  - 80

A example configuration

And here’s how all those different variables and configurations look like in a very basic docker-compose.yml file.

version: '3'

services:
  example-app:
    image: example/example-app
    expose:
      - 80
    environment:
      VIRTUAL_HOST: app.example.com
      LETSENCRYPT_HOST: app.example.com
      LETSENCRYPT_EMAIL: [email protected]

networks:
    default:
        external:
            name: nginx-proxy

At this point, you should have everything you need to know to deploy all kinds of Docker containers under this SSL-enabled proxy. Let’s look at a few examples to show you how it works in the real world.

Leading by example: Miniflux

Remember how I wanted to self-host Miniflux at the feeds.example.com domain? Here’s the docker-compose.yml that I came up with. Note the expose, environment, and networks configurations.

version: '2'

services:
  miniflux:
    image: miniflux/miniflux:latest
    expose:
      - 80
    volumes:
      - miniflux_data:/var/www/html/data
    environment:
      VIRTUAL_HOST: feeds.example.com
      LETSENCRYPT_HOST: feeds.example.com
      LETSENCRYPT_EMAIL: [email protected]

volumes:
  miniflux_data:
        driver: local

networks:
    default:
        external:
            name: nginx-proxy

Initialize the container with docker-compose up -d and you’ll have an SSL-enabled feed reader in about 30 seconds!

Leading by example, take 2: WordPress

In my previous tutorial on nginx-proxy, I showed off how easy it is to launch a WordPress instance running behind the proxy.

It’s just as easy to do it with SSL. I took the exact same docker-compose.yml file and simply added the LETSENCRYPT_HOST and LETSENCRYPT_EMAIL environment variables. All you have to do is change those, plus VIRTUAL_HOST, to suit your needs.

version: "3"

services:
   db:
     image: mysql:5.7
     volumes:
        - db_data:/var/lib/mysql
     restart: always
     environment:
        MYSQL_ROOT_PASSWORD: somewordpress
        MYSQL_DATABASE: wordpress
        MYSQL_USER: wordpress
        MYSQL_PASSWORD: wordpress
     container_name: wp_test_db

   wordpress:
     depends_on:
        - db
     image: wordpress:latest
     expose:
        - 80
     restart: always
     environment:
        VIRTUAL_HOST: blog.example.com
        LETSENCRYPT_HOST: blog.example.com
        LETSENCRYPT_EMAIL: [email protected]
        WORDPRESS_DB_HOST: db:3306
        WORDPRESS_DB_USER: wordpress
        WORDPRESS_DB_PASSWORD: wordpress
     container_name: wp_test
volumes:
  db_data:

networks:
  default:
    external:
      name: nginx-proxy

Run it with docker-compose up -d and then visit your domain—there’s that beautiful WordPress installation page with SSL encryption.

Sweet.

Wrapping up

This has been a pretty extensive tutorial, so I understand if you’re feeling a little overwhelmed. My goal here isn’t to guide you toward any specific end goal, but rather to give you the tools to leverage the fantastic nginx-proxy project to your unique needs.

If you keep the required settings in mind, you should be able to put almost any containerized app behind this proxy. And that gives you a ton of power to get the most out of your VPS.

Best of all, docker-letsencrypt-nginx-proxy-companion will automatically renew your certificates for you, so there’s no need to check in or do any manual interventions.

As far as I can tell, this is the best way to serve many, if not dozens of SSL-encrypted websites and apps via a single proxy and a single VPS.

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

Our most popular posts: