Setting up a free SSL certificate with Docker and Let’s Encrypt can be a little tricky. Let’s Encrypt certificates are renewed every 90 days and the process needs to write a ‘proof of ownership’ to your domain. Certbot from the Electronic Frontier Foundation is a command-line tool that automates this process.

A common container pattern for deploying web applications with docker looks a bit like this:

A common docker design pattern with nginx and two web-apps

An NGINX container running out front, routing traffic to custom application containers. These routes might either be by port forwarding or perhaps, in the case of more of a static blog type of thing, NGINX could be reading straight from a volume.

To start encrypting traffic to this sort of container pattern, I add an extra certbot container. This new container writes ownership proofs to the applications and stores the SSL certificates so NGINX can access them.

A common docker design pattern with nginx, two web-apps and SSL certificates from Let's Encrypt.

The dockerfile for this certbot container is small. It downloads and installs the certbot-auto client and then spins up a cron job to periodically renew the certificate.

FROM ubuntu:14.04

MAINTAINER Reprage

ADD crontab /etc/cron.d/certbot-cron
RUN chmod 0644 /etc/cron.d/certbot-cron
RUN touch /var/log/cron.log

RUN apt-get update
RUN apt-get -y install wget
RUN apt-get -y install cron

RUN ls
RUN wget https://dl.eff.org/certbot-auto
RUN chmod a+x certbot-auto
RUN ./certbot-auto --os-packages-only -n

CMD cron && tail -f /var/log/cron.log

The schedule for the cron job follows the recommendations from Let’s Encrypt; a random minute within the hour, twice a day. The renewal process doesn’t do anything if the SSL certificates haven’t expired. Yet, checking twice a day helps you stay online in case Let’s Encrypt needs to revoke your existing certificate for some reason.

The cron tab looks like this:

1 6,18 * * * sleep $(expr $RANDOM % 55)m; /./certbot-auto renew --quiet --no-self-upgrade

Now the new certbot container needs to be hooked into the rest of your infrastructure. This can be done with compose, or directly with the docker command:

docker run -v /etc/letsencrypt:/etc/letsencrypt --volumes-from app1 --volumes-from app2 -i -t certbot /bin/bash

This maps the output directory for letsencrypt to the underlying file system and opens volumes from app1 and app2. It spins up bash inside the certbot container so you can get the initial certificates with certbot.

./certbot-auto certonly --webroot -w /app1/_site -d app1.com
./certbot-auto certonly --webroot -w /app2/_site -d app2.com

In the above example certbot is expecting to be able to modify your webroot directly. It writes a little proof to the directory:

${webroot-path}/.well-known/acme-challenge

If you are running a dynamic web application, you might need to do some internal routing gymnastics within your application so that Let’s Encrypt can read your ownership proof.

Next is configuring NGINX to use the SSL certificates from Let’s Encrypt, edit your site configuration to something like:

server {
  listen 443 ssl http2;
  server_name app1;

  ssl_certificate /etc/letsencrypt/live/app1.com/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/app1.com/privkey.pem;

  # ... The rest of your NGINX configuration.
}

server {
  listen 80;
  server_name app1.com;
  return 301 https://$host$uri;
}

All that is left is to spin up your updated nginx container, remembering to map etc/letsencrypt to get the containers from certbot and to open up 443 for SSL traffic.

docker run -p 80:80 -p 443:433 -v /etc/letsencrypt:/etc/letsencrypt --restart always --name nginx --volumes-from app1 --volumes-from app2 -t -i nginx

A little note on testing and development. Seeing as all HTTP traffic is being redirected as HTTPS traffic, I needed local SSL certs for development. In my case I just took an old private key / certificate pair and have that on my local machine. Naturally I get HTTPS warnings in the browser. Haven’t worked out a better alternative yet, maybe automatically generating a self-signed certificate when bootstrapping my development environment?

References

comments powered by Disqus