I’ve been using Docker for a while now. The idea of running everything the same anywhere is like a dream come true and not having to worry about updating a package dependency on the server and breaking everything. For example the same server running multiple Python applications which require different versions. Ever since I moved from Digital Ocean to Scaleway I have begun to use Docker for almost everything I run on my Scaleway servers.

A while ago my colleague told me about nginx-proxy. The idea of having a seperate HTTP proxy that takes care of redirecting traffic to any of my web application containers, along with providing SSL/TLS certificates using Let’s Encrypt, without having to worry much about setup and configuration for each domain. I decided to try it out and afterwards I got it working, I decided to write about my setup so others could benefit as well.

Prerequisite is of course Docker. If you don’t know it, I recommend their getting started guide. This however is not a definite advanced Docker guide. I’m only going through the basics.

Overview

For starters, here’s a high level overview of what this setup consists of. I’m using the more secure approach for setting up nginx-proxy with seperate containers for docker-gen and nginx. You don’t want to open a connection straight to the Docker socket file. If you do, you are in risk of a security vulnerability because a hacker could get access to everything running inside of Docker.

nginx proxy overview

To explain the image above, we begin by setting up the network nginx-proxy that all of our containers will use to communicate with each other. I will be setting up four containers total and one of them will be my web application container.

Docker images

Let’s begin with defining what Docker images we are going to use.

nginx

This is just a simple nginx web server based on the official nginx Docker image and will be at the front listening to ports 80 (HTTP) and 443 (HTTPS) acting as a proxy. This container will have access to the mounted volumes and read configuration generated by the other containers.

https://hub.docker.com/_/nginx/

docker-gen

This container will connect to the Docker socket file and listen to Docker events. When containers start, stop, etc., it will generate nginx virtual host configurations based on these events and domains/ports specified in the VIRTUAL_* environment variables. These configuration files will be mounted as volumes and accessible to the other containers.

https://hub.docker.com/r/jwilder/docker-gen/

letsencrypt-nginx-proxy-companion

This container will connect to the Docker socket file and also listen to Docker events. When containers start, stop, etc., it will generate SSL/TLS certificates provided by Let’s Encrypt and modify nginx vhost configuration files. These certificates will be mounted as volumes and accessible to the nginx container, so it can use the certificates and serve the web application containers securely via HTTPS.

https://hub.docker.com/r/jrcs/letsencrypt-nginx-proxy-companion/

Web application container

This is one of our web application containers. It contains our web application and shares the same network nginx-proxy as the docker-gen and letsencrypt-nginx-proxy-companion containers. The nginx container will redirect traffic to this container, if it matches the domain set up in the environment variables.

This is just one container, but they can be multiple and set up for each domain, each with their own configuration and certificates.

Docker volumes

I’m using named volumes so I can always reference them by a specific name instead of some path. They are then stored under /var/lib/docker/volumes/volume-name/ so it’s easy to find and backup them.

nginx-conf

This volume is mounted to /etc/nginx/conf.d/default.conf in the container. The docker-gen container will listen to Docker events, when containers start/stop/etc. and modify this file. This file contains for example IP addresses of our web application containers. Then the front-most nginx container will read this file and forward traffic based on it.

nginx-vhost

This volume is mounted to /etc/nginx/vhost.d/ in the container and can be used to override virtual host configuration for domains. You should never need to do this but it can come in handy if you want to override some generated configuration.

nginx-certs

This volume is mounted to /etc/nginx/certs/ in the container and will contain all SSL/TLS certificates for domains. The letsencrypt-nginx-proxy-companion container will listen to Docker events, when containers start/stop/etc. and modify the content of this folder with valid Let’s Encrypt certificates. Then the front-most nginx container will use them.

Setup

Create Docker network

Let’s begin by creating a new Docker bridged network, that we will use to connect the nginx, docker-gen and letsencrypt containers to our web application containers.

Create Docker volumes

Next create three Docker volumes for nginx domains configuration and SSL/TLS certificates. The path will be something like /var/lib/docker/volumes/[name-of-volume]/_data/ so you can always easily find them.

Starting Docker containers

I’m just going to start the containers with docker run, one by one. Then I’m going to show you how you can do it in a more clean way with Docker Compose.

nginx

Let’s first start the nginx container.

It uses port 80 and 443, because we want both HTTP and HTTPS. We use the label so the letsencrypt-nginx-proxy-companion container can target the container.

nginx-gen

Next let’s start the docker-gen container and name it nginx-gen.

It uses volumes from the nginx container and mounts straight to the Docker socket file.

The template is just a template that it uses to generate the nginx configuration file. The template is accessible here

For the other parameters you can read about them here.

You may have to specify full path to nginx.tmpl file instead of ./nginx.tmpl. You can get it here.

nginx-letsencrypt

Next let’s start the letsencrypt-nginx-proxy-companion container and name it nginx-letsencrypt.

It uses also volumes from the nginx container and mounts straight to the Docker socket file.

First we have to specify an environment variable NGINX_DOCKER_GEN_CONTAINER that targets the docker-gen container. We named it nginx-gen so we use that.

Web application container

Last but not least let’s start our web application container, that holds our application we want to proxy to.

We have to set the following environment variables:

  • VIRTUAL_HOST: This is the domain you want to map to the container. This will let docker-gen generate a nginx virtual host config for that domain and direct it to this containers IP address. This can be multiple domains, seperated by a comma. Default is port 80 but you can change it with VIRTUAL_PORT.
  • LETSENCRYPT_HOST: This registers a Let’s Encrypt certificate for the domain. This can be multiple domains, seperated by a comma.
  • LETSENCRYPT_EMAIL: This connects the Let’s Encrypt certificate to that email so they know the owner and can contact you when certificates are expiring.

Here’s the command to run the container:

Now all of the containers should be running and you should be able to access that domain and be redirected to your web application container. It could take a while to get HTTPS running, because it takes minute or two to get the certificates.

Docker Compose

If you’re not familiar with Docker Compose it’s basically a tool that eases the process of running multiple containers at the same time configured a certain way. It makes it easy to get everything running and always keeping it running the same way.

I created a docker-compose.yml file that runs the proxy container setup above. But first you have to create the network and volumes. Then you just need to run your web application containers and specify the environment variables when you run it. You can also add it the docker-compose.yml file. But I choose to be able to have it seperate from the proxy setup.

You can check it out here: https://github.com/gaui/dockerc-nginx-letsencrypt-proxy

You may have to specify full path to nginx.tmpl file instead of ./nginx.tmpl. You can get it here.

You just have to learn these basic commands: docker-compose [up -d|down|start|stop|restart]. It will take care of the rest for you. I highly recommend it.

PHP 7

For my scenario I’m running WordPress (PHP + MySQL) so I had to set up a nginx PHP 7 web application container, so I created a Docker image with PHP 7 FPM + utils (cli, cgi, mysqli).

So above instead of running nginx:1.12 image for your web application container, you would just use gaui/nginx-php7:1.0 instead.

Credits

All credits go to jwilder and JrCs for their awesome stuff. And also of course to Docker, nginx and Let’s Encrypt.