Adding SSL certs to NGINX docker container

My solution:

I have mapped /etc/letsencrypt/live/mydomain.com/ to /etc/nginx/certs/ by:

volumes:
  - /etc/letsencrypt/live/mydomain.com/:/etc/nginx/certs/

In this case, it has mapped the soft symbolic link.

lrwxrwxrwx 1 root root   38 Sep 15 00:21 chain.pem -> ../../archive/mydomain.com/chain1.pem
lrwxrwxrwx 1 root root   42 Sep 15 00:21 fullchain.pem -> ../../archive/mydomain.com/fullchain1.pem
lrwxrwxrwx 1 root root   40 Sep 15 00:21 privkey.pem -> ../../archive/mydomain.com/privkey1.pem

Finally, I changed the volume section as the following:

volumes:
  - /etc/letsencrypt/archive/mydomain.com/:/etc/nginx/certs/

Finally cracked this and was able to successfully repeat the process on my dev and production site to get SSL certs working!

Sorry for the length of the post!

In my setup I have docker docker-compose setup on an ubuntu 16 machine.

Anyone who's encountering this problem I'll detail the steps I did.

  1. Go to the directory where your code lives

    cd /opt/example_dir/

  2. Make a directory for letsencrypt and it's site.

    sudo mkdir -p /opt/example_dir/letsencrypt/letsencrypt-site

  3. Create barebones docker-compose.yml file from the letsencrypt directory.

    sudo nano /opt/example_dir/letsencrypt/docker-compose.yml

Add the following to it:

    version: '2'

        services:
            image: nginx:latest
            ports:
              - "80:80"
            volumes:
              - ./nginx.conf:/etc/nginx/conf.d/default.conf
              - ./letsencrypt-site:/usr/share/nginx/html
            networks:
              - docker-network

        networks:
          docker-network:
            driver: bridge

* This will pull down the latest nginx version
* Expose port 80 
* Mount a config file (that i'll create later) 
* Maps the site directory so that we can have a simple test index.html for when 

we start the simple nginx container.

  1. Create an nginx.conf file in /opt/example_dir/letsencrypt

    sudo nano /opt/example_dir/letsencrypt/nginx.conf

Put the following into it

    server {
      listen 80;
      listen [::]:80;
      server_name example_server.com;

      location ~ /.well-known/acme-challenge {
          allow all;
          root /usr/share/nginx/html;
      }

      root /usr/share/nginx/html;
      index index.html;
    }

* This listens for requests on port 80 for the server with name example_server.com
* Gives the Certbot agent access to ./well-known/acme-challenge
* Sets the default root and file
  1. Next create an index.html file within /opt/example_dir/letsencrypt/letsencrypt-site

sudo nano /opt/example_dir/letsencrypt/letsencrypt-site/index.html

Add the following to it

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>LetsEncrypt Setup</title>
</head>
<body>

    <p>Test file for our http nginx server</p>
</body>
</html>

#### All parts in place for basic nginx container!

  1. Now we start up the nginx container.

    cd /opt/example_dir/letsencrypt
    sudo docker-compose up -d
    

The nginx container is up and running now, visit the url you've defined and you should get the test index.html page back. At this point we're ready to run the certbot command to generate some certs

  1. Run the following to generate certs replacing --email with your email

    sudo docker run -it --rm \
    -v /docker-volumes/etc/letsencrypt:/etc/letsencrypt \
    -v /docker-volumes/var/lib/letsencrypt:/var/lib/letsencrypt \
    -v /opt/example_dir/letsencrypt/letsencrypt-site:/data/letsencrypt \
    -v "/docker-volumes/var/log/letsencrypt:/var/log/letsencrypt" \
    certbot/certbot \
    certonly --webroot \
    --email [email protected] --agree-tos --no-eff-email \
    --webroot-path=/data/letsencrypt \
    -d example.com
    
    • Run docker in interactive mode so you can see the output.
    • When its finished generating certs it will remove itself.
    • It will mount 4 volumes:
      1. The letsencrypt folder where the certs are stored/
      2. A lib folder
      3. Maps our site folder
      4. Maps a logging path
    • It agrees to ToS
    • Specifies the root webpath
    • Specify the server address you want to generate certs for.

If that command ran okay then we have generated certs for this web server. We can now use these in our production site and configure nginx to use the ssl and make use of these certs!

  1. Shut down the nginx container

    cd /opt/example_dir/letsencrypt/
    sudo docker-compose down
    

Setup Production nginx container

Directory structure should look like this now. Where you have your code / web app project and then the letsencrypt folder that we created above.

/opt/example_dir
             / -> project_folder
             / -> letsencrypt
  1. Create a folder call dh-param

    sudo mkdir -p /opt/example_dir/project_folder/dh-param
    
  2. Generate a dh key

    sudo openssl dhparam -out /opt/example_dir/project_folder/dh-param/dhparam-2048.pem 2048
    
  3. Update docker-compose.yml and nginx.conf files within /opt/example_dir/project_folder

The project_folder is where my source code lives so I create a production config file here for nginx and update the docker-compose.yml to mount my nginx config, dh-pharam exchange key as well as the certs themselves we created earlier.

nginx service in the docker-compose

    nginx:
        image: nginx:1.11.3
        restart: always
        ports:
          - "80:80"
          - "443:443"
          - "8000:8000"
        volumes:
          - ./nginx.conf:/etc/nginx/conf.d/default.conf
          - ./dh-param/dhparam-2048.pem:/etc/ssl/certs/dhparam-2048.pem
          - /docker-volumes/etc/letsencrypt/live/exampleserver.com/fullchain.pem:/etc/letsencrypt/live/exampleserver.com/fullchain.pem
          - /docker-volumes/etc/letsencrypt/live/exampleserver.com/privkey.pem:/etc/letsencrypt/live/exampleserver.com/privkey.pem
        networks:
          - docker-network

        volumes_from:
          - flask
        depends_on:
          - flask
          - falcon
        links:
          - datastore

nginx.conf within project_folder

error_log /var/log/nginx/error.log warn;

server {
    listen 80;
    listen [::]:80;

    server_name exampleserver.com

    location / {
        rewrite ^ https://$host$request_uri? permanent;
    }

    #for certbot challenges (renewal process)
    location ~ /.well-known/acme-challenge {
        allow all;
        root /data/letsencrypt;
    }
}

#https://exampleserver.com
server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;

    server_name exampleserver.com;

    server_tokens off;

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

    ssl_buffer_size 8k;

    ssl_dhparam /etc/ssl/certs/dhparam-2048.pem;

    ssl_protocols TLSv1.2 TLSv1.1 TLSv1;
    ssl_prefer_server_ciphers on;

    ssl_ciphers ECDH+AESGCM:ECDH+AES256:ECDH+AES128:DH+3DES:!ADH:!AECDH:!MD5;

    ssl_ecdh_curve secp384r1;
    ssl_session_tickets off;

    # OCSP stapling
    ssl_stapling on;
    ssl_stapling_verify on;
    resolver 8.8.8.8;


    # Define the specified charset to the “Content-Type” response header field
    charset utf-8;
}

At this point everything is setup! (finally)

  1. Spin up the docker container.

    cd /opt/example_dir/project_folder
    sudo docker-compose up -d
    
    # Check the docker log with:
    sudo docker logs -f -t  
    

I know it's a lot of steps but this is what I have done, it's worked for me and hope it helps someone else.