Let's Encrypt Certificate Generation Using Docker

Easily obtain a SSL certificate using Let's Encrypt and Docker

At this point, you have probably heard of Let’s Encrypt, a CA that provides free SSL certificates. Obtaining a free SSL certificate for a domain only requires proof of ownership. Doing so is very straightforward using their command line utility, letsencrypt. Because they do a great job explaining how it works behind the scenes, this article will focus on quickly getting a certificate for the domain “mydomain.acme.com”.

Simply put, the registration process looks like this:

  • Let’s Encrypt registration command is run for “mydomain.acme.com”
  • The command generates a secret path on the host running the command (ex. /var/www/.well-known/acme-challenge/DMVhmQfxgARbHWn62xl5AI4ep9quvidZvtQzFUikEy4)
  • The command then asks that the validation servers perform a HTTP GET on that path (ex. http://mydomain.acme.com/.well-known/acme-challenge/DMVhmQfxgARbHWn62xl5AI4ep9quvidZvtQzFUikEy4)
  • If the server response content matches that of the generated secret, then the certificates will be downloaded to /etc/letsencrypt on the machine running the command

In this article, I’ll briefly should how to use Docker to host a webserver via a nginx container and then start a Let’s Encrypt container to register our domain. Finally, we will copy the certificates from the Let’s Encrypt container to the hosts we are running the Docker commands on so we can distribute them elsewhere.

If you already have webservers running for your domain, then you’ll probably need to do some tinkering with your loadbalancer to proxy the traffic from the Let’s Encrypt servers to the right place in your environment.

Getting Ready

First, we are going to need:

To start off, we will run Docker Machine with the Digital Ocean driver to create a brand new server instance and install the Docker daemon. The command looks like this:

$ docker-machine create -d digitalocean \
  --digitalocean-access-token <access_token> \
  letsencrypt

Once Docker is installed on our new instance, we can setup our environment to use the remote Docker daemon:

$ eval $(docker-machine env letsencrypt)

And verify it is working with:

$ docker info

Finally, we need the public IP of the instance so we can update our domain’s A record:

$ docker-machine ip letsencrypt

Depending on your DNS provider, the steps for updating the domain’s A record with the public IP of the instance will look different. For Amazon’s Route53 service, it looks like the following:

Updating DNS record for domain

Let’s Encrypt!

Now come’s the fun part. We start by running an Nginx container listening on port 80 and exposing the container path /usr/share/nginx/html as a volume. This will serve up the verification files for the Let’s Encrypt registration process.

$ docker run -d \
  -p 80:80 \
  --name nginx \
  -v /usr/share/nginx/html \
  nginx

Next we will start the Let’s Encrypt container:

$ docker run -it \
  --name letsencrypt \
  --volumes-from nginx \
  quay.io/letsencrypt/letsencrypt \
  certonly \
  --agree-tos \
  --webroot \
  --webroot-path /usr/share/nginx/html \
  -m [email protected] \
  -d mydomain.acme.com

Let’s take a look at what’s going on here. The --volumes-from flag allows for the “letsencrypt” container to modify files on the “nginx” container’s filesystem at the specified path (/usr/share/nginx/html). By running the letsencrypt container with the --webroot --webroot-path /usr/share/nginx/html flags, the generated secret path will automatically be served up by the nginx container. Finally, we provide an email address (-m [email protected], not used for verification) and the domain we wish to secure (-d mydomain.acme.com).

If successful, the certificates will be downloaded to the letsencrypt container under /etc/letsencrypt. To retrieve them, we can copy them straight out of the container and onto our local box:

$ docker cp letsencrypt:/etc/letsencrypt/ letsencrypt

To view the certificates:

$ ls letsencrypt/archive/mydomain.acme.com/
cert1.pem  chain1.pem  fullchain1.pem  privkey1.pem

We can also verify that the Let’s Encrypt validation servers hit our nginx container:

$ docker logs nginx
x.x.x.x - - [21/Apr/2016:20:45:32 +0000] "GET /.well-known/acme-challenge/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx HTTP/1.1" 200 87 "-" "Mozilla/5.0 (compatible; Let's Encrypt validation server; +https://www.letsencrypt.org)" "-"

Finally, we can cleanup our Docker containers

$ docker rm -f nginx letsencrypt

Or just erase the entire instance altogether:

$ docker-machine rm letsencrypt