Docker, NGINX proxy, and Let's Encrypt

Docker, NGINX proxy, and Let's Encrypt

There are plenty of tutorials out there on how to work with Docker and the various tools available for it, however, there isn't many that take you from start to finish when combining it all together. In this post I want to go over how I set this up on my Digital Ocean Linux Droplet.

Installing Docker

The first thing to do of course is install Docker. You can always use a droplet with Docker pre-installed, but in case you didn't select that option when setting up the droplet here is how:

1. update machine and dependencies:

sudo apt-get update
sudo apt install \
    apt-transport-https \
    ca-certificates \
    curl \
    gnupg2 \
    software-properties-common

2. add Docker's official GPG key:

curl -fsSL https://download.docker.com/linux/debian/gpg | sudo apt-key add -

3. set up the stable repository:

sudo add-apt-repository \
    "deb [arch=amd64] https://download.docker.com/linux/debian \
    $(lsb_release -cs) \
    stable"

4. update the package database to reflect newly added repo:

sudo apt update

5. Ensure that the above config took effect by checking for install candidates:

apt-cache policy docker-ce

You should see something like this perhaps with different version numbers:

docker-ce:
  Installed: (none)
  Candidate: 5:19.03.11~3-0~debian-buster
  Version table:
     5:19.03.11~3-0~debian-buster 500
        500 https://download.docker.com/linux/debian buster/stable amd64 Packages

6. If the above is similar then go ahead and install Docker:

sudo apt install docker-ce

7. You can ensure that it is installed by running:

sudo systemctl status docker

Configuring Docker

Next I want to be able to run docker commands without having to type sudo each time by adding my username to the docker group.

sudo usermod -aG docker ${USER}

After that we have to logout and back in to see the changes take place. To verify we run:

id -nG

Create a Docker Network

Since we will be running our apps and websites in containers, we need to create a network for them to communicate over.

docker network create nginx-proxy

Now we can reference this network when we spin up our other services with docker-compose

Configuring NGINX

NGINX will sit in front of all our services and forward a request to the appropriate container. We'll also set it up so that it can register new services with Let's Encrypt automatically so that any new services added can easliy be accessed over https.

The following docker-compose file should work for you no problem. Just place it somewhere like ~/nginx-proxy/docker-compose.yml So long as you are in the same folder you can run docker-compose up and the services will start doing their thing. You can verify that the proxy is routing traffic correctly with the whoami container. If you run curl -H "Host: whoami.local" localhost and you get a response then the nginx-proxy correctly routed your request to the whoami container. Next we'll want to route to our other containers but with real URLS.

version: '2'
services:
  nginx:
    container_name: nginx
    image: jwilder/nginx-proxy
    restart: always
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - vhost:/etc/nginx/vhost.d
      - html:/usr/share/nginx/html
      - ./certs:/etc/nginx/certs:ro
      - /var/run/docker.sock:/tmp/docker.sock:ro
    labels:
      - "com.github.jrcs.letsencrypt_nginx_proxy_companion.nginx_proxy"

  nginx-letsencrypt:
    container_name: nginx-letsencrypt
    image: jrcs/letsencrypt-nginx-proxy-companion
    depends_on:
      - nginx
    environment:
      - NGINX_DOCKER_GEN_CONTAINER=dockergen
    volumes_from:
      - nginx
    volumes:
      - ./certs:/etc/nginx/certs:rw
      - /var/run/docker.sock:/var/run/docker.sock:ro

  dockergen:
    container_name: dockergen
    image: jwilder/docker-gen
    command: -notify-sighup nginx -watch /etc/docker-gen/templates/nginx.tmpl /etc/nginx/conf.d/default.conf
    volumes_from:
      - nginx
    volumes:
      - ./nginx.tmpl:/etc/docker-gen/templates/nginx.tmpl:ro

  whoami:
    image: jwilder/whoami
    environment:
      - VIRTUAL_HOST=whoami.local

volumes:
  vhost:
  html:

networks:
  default:
    external:
      name: nginx-proxy

Configuring Containers

The final step is to start adding applications. I'll post what I have used to spin up a WordPress instance. Notice the Environment variables I set in order to get the container registered with NGINX proxy and the Let's Encrypt Companion. This makes it so that as long as we have a URL pointing to our droplet IP our NGINX container will be able to route it to the correct application.

version: '2'

services:
    wordpress:
        image: wordpress:5.4.1-php7.2-apache
        container_name: ${SITE_NAME}-wp
        ports:
            - 80
        environment:
            WORDPRESS_TABLE_PREFIX: wp_
            WORDPRESS_DB_HOST: mysql_${SITE_NAME}_${SALT}:3306
            WORDPRESS_DB_NAME: wp_${SITE_NAME}_${SALT}_db
            WORDPRESS_DB_USER: ${SITE_NAME}_${SALT}_user
            WORDPRESS_DB_PASSWORD: ${SITE_DB_PSW}
            VIRTUAL_HOST: ${SITE_URLS}
            LETSENCRYPT_HOST: ${SITE_URLS}
            LETSENCRYPT_EMAIL: ${LETSENCRYPT_EMAIL}
        volumes:
            - data_volume:/var/www/html
            - ./home/wp:/home/wp
        links:
            - mysql:mysql_${SITE_NAME}_${SALT}
  
    mysql:
        image: mariadb
        container_name: ${SITE_NAME}-mysql
        environment:
            MYSQL_DATABASE: wp_${SITE_NAME}_${SALT}_db
            MYSQL_USER: ${SITE_NAME}_${SALT}_user
            MYSQL_PASSWORD: ${SITE_DB_PSW}
            MYSQL_ROOT_PASSWORD: ${SITE_DB_PSW_ROOT}
            MYSQL_RANDOM_ROOT_PASSWORD: "yes"
        volumes:
            - db_data:/var/lib/mysql
            - ./home/db:/home/db
  
    wp:
        image: pattonwebz/docker-wpcli
        container_name: ${SITE_NAME}-wpcli
        volumes_from:
            - wordpress
        links:
            - mysql:mysql_${SITE_NAME}_${SALT}
        entrypoint: wp
        command: "--info"
  
volumes:
    db_data:
    data_volume:
  
networks:
    default:
        external:
            name: nginx-proxy

Note that I am using environment variables for in the docker-compose file. You can set these using a .env file in the same folder where the docker-compose file is.