If you have a dockerized webapp and you want to deploy an https version of it, with the least amount of buttons pushed, you’re in the right place!

In addition to designing the acme protocol, creating the open source letsencrypt client and issuing free certificates to anyone! The good people at Let’s Encrypt also make an official client docker image available. This blog post will show you how to incorporate their official image into a docker compose service that automatically sets up everything needed to get a signed SSL cert.

This repo uses docker-compose so you’ll need to install that if you dont already have it.

Thanks to Martin Brugger for his example nginx config upon which our nginx config is based.

How it works

Once you have the repo installed (instructions after this section), you’ll be able to bring up the service with: docker-compose up
When you do this, docker-compose starts an nginx reverse proxy, your app container, and the official letsencrypt image.

The proxy image’s init script starts nginx in a temporary ‘initialisation’ config:

Using Let's Encrypt and Docker for Automatic SSL

events { worker_connections 1024; }
http {
    server {
        listen 80;
        server_name ___my.example.com___;

        location /.well-known/acme-challenge {
            proxy_pass http://___LETSENCRYPT_IP___:___LETSENCRYPT_PORT___;
            proxy_set_header Host            $host;
            proxy_set_header X-Forwarded-For $remote_addr;
            proxy_set_header X-Forwarded-Proto https;
        }

        location / {
            proxy_pass http://___APPLICATION_IP___:___APPLICATION_PORT___;
            proxy_set_header Host            $host;
            proxy_set_header X-Forwarded-For $remote_addr;
        }

    }
}

The initial config allows letsencrypt’s acme challenge to get to the letsencrypt container. The letsencrypt container runs in standalone mode, connecting to letsencrypt.org to make the cert request and then waiting on port 80 for the acme-challenge.

When letsencrypt issues the challenge request, the letsencrypt client writes the certs to /etc/letsencrypt, which is a volume mounted to the nginx container. The nginx container’s init script notices the certs appear, creates a new nginx config and restarts nginx.

Using Let's Encrypt and Docker for Automatic SSL

events { worker_connections 1024; }
http {
    server {
        listen 80;
        server_name ___my.example.com___;

        location /.well-known/acme-challenge {
            proxy_pass http://___LETSENCRYPT_IP___:___LETSENCRYPT_PORT___;
            proxy_set_header Host            $host;
            proxy_set_header X-Forwarded-For $remote_addr;
            proxy_set_header X-Forwarded-Proto https;
        }

        location / {
            return         301 https://$server_name$request_uri;
        }

    }

    server {
        listen 443;
        server_name ___my.example.com___;

        ssl on;
        ssl_certificate /etc/letsencrypt/live/___my.example.com___/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/___my.example.com___/privkey.pem;
        ssl_session_timeout 5m;
        ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
        ssl_ciphers 'EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH';
        ssl_prefer_server_ciphers on;

        ssl_session_cache shared:SSL:10m;
        ssl_dhparam /etc/ssl/private/dhparams.pem;

        location /.well-known/acme-challenge {
            proxy_pass http://___LETSENCRYPT_HTTPS_IP___:___LETSENCRYPT_HTTPS_PORT___;
            proxy_set_header Host            $host;
            proxy_set_header X-Forwarded-For $remote_addr;
            proxy_set_header X-Forwarded-Proto https;
        }

        location / {
            proxy_pass http://___APPLICATION_IP___:___APPLICATION_PORT___;
            proxy_set_header Host            $host;
            proxy_set_header X-Forwarded-For $remote_addr;
        }
    }
}

The service is now available over https.

How to run it

Before integrating your app, you can run the service as-is because a simple test server docker image is included. Here’s how to do that:

You need a docker server running on the public internet, with a DNS entry pointing at it. Otherwise, you wont be able to catch letsencrypt’s acme challenge. If you don’t have an internet-connected docker server yet then see my previous post to get started.

git clone https://bitbucket.org/automationlogic/le-docker-compose.git
cd le-docker-compose

Open up docker-compose.yml and replace the two instances of www.yourdomain.co.uk , adding the dns name that your docker server is running at. Then:

docker-compose  build
docker-compose  up

An example app is now running securely. Try it with your browser!

When all the https setup is finished and your server is up, the letsencrypt container exits. This is what we want; the letsencrypt container only needs to be up when it is requesting a cert or renewing one.

Integrate your app

To use this with your own dockerised web server, edit docker-compose again.

Replace this section:

app:
  build: mock_server
  ports:
    - "80"

with

app:
  image: yourimage
  ports:
    - "80"

and then run

docker-compose rm -f
docker-compose up

 

Renew your certificate

Start the letsencrypt container with docker compose. The container starts, runs the acme process, and exits.

docker-compose run letsencrypt

Then, reload the nginx config

docker exec ledockercompose_nginx_1 nginx -s reload

And there’s no reason you can’t just put that in your crontab leaving you to worry about something more interesting than renewing your SSL certificates!