{"id":609,"date":"2018-03-19T23:20:30","date_gmt":"2018-03-19T23:20:30","guid":{"rendered":"https:\/\/www.45rpmsoftware.com\/wordpress\/?p=609"},"modified":"2018-03-22T15:31:50","modified_gmt":"2018-03-22T15:31:50","slug":"docker-lab-containerising-your-website-part-3-lets-encrypt","status":"publish","type":"post","link":"https:\/\/www.45rpmsoftware.com\/blog\/?p=609","title":{"rendered":"Docker Lab \u2013 Containerising your website, Part 4 (Let&#8217;s Encrypt)"},"content":{"rendered":"<p>If you&#8217;ve been following along with this series of tutorials, you&#8217;ll have built a LEMP stack capable of handling multiple vhosts. That&#8217;s all very well &#8211; but if you expect your users to enter their username and password into the site then you&#8217;ll need to provide them with a little security. The only way to do that is with ssl, certificates for which used to be pricey or came with strings attached.<\/p>\n<p><a href=\"https:\/\/letsencrypt.org\">Let&#8217;s Encrypt<\/a>\u00a0is a certificate authority started by the Electronic Frontier Foundation, Mozilla Foundation, University of Michigan, Akamai Technologies and Cisco Systems to provide ssl certificates for free, and to simplify the process of securing your website into the bargain. They take donations though &#8211; and this is one project that you really should consider supporting.<!--more--><\/p>\n<p>I&#8217;ve used Let&#8217;s Encrypt on other websites and the process has always been very simple &#8211; so I naively assumed that securing my Dockerised website would be nice and simple too. It wasn&#8217;t. It&#8217;s not that the process is particularly difficult or time consuming &#8211; it&#8217;s just that it took a little while to work out the incantations.<\/p>\n<p>These instructions assume that you&#8217;ve followed the steps in the other tutorials of this series.<\/p>\n<h2>Configuring your Maintenance Container<\/h2>\n<p>It may be possible to generate your certificates in your main website container but if it is then I couldn&#8217;t work out how to do it easily. I created a new container just for maintenance.<\/p>\n<pre>cd ~ \r\nmkdir -p maint-compose\/public \r\nmkdir -p lemp-compose\/certificates<\/pre>\n<p>Now copy your existing certificates out of the nginx container. This step may not be necessary, but since the nginx configuration references these dummy certificates it&#8217;s do this step or remove the references to the dummies in the nginx.conf. I leave it up to you to decide which you prefer.<\/p>\n<pre>cd ~\/lemp-compose\/certificates\r\ndocker cp lempcompose_nginx_1:\/opt\/bitnami\/nginx\/conf\/bitnami\/certs\/server.crt .\r\ndocker cp lempcompose_nginx_1:\/opt\/bitnami\/nginx\/conf\/bitnami\/certs\/server.key .<\/pre>\n<h2>Setting up the Maintenance Container<\/h2>\n<p>Next create your configuration file &#8211; docker-compose.yml. \u00a0You might use vi or emacs to edit the file, but I prefer the easy life so I&#8217;ll be using nano. \u00a0The important thing is what the yml file contains:<\/p>\n<pre>cd ~\/maint-compose \r\nnano docker-compose.yml<\/pre>\n<p>Add the following lines to the docker-compose.yml file<\/p>\n<pre>version: '2'\r\n\r\nservices:\r\n nginx:\r\n image: 'bitnami\/nginx:latest'\r\n ports:\r\n - '8080:8080'\r\n - '80:80'\r\n volumes:\r\n - .\/public:\/app\r\n - ..\/lemp-compose\/certificates:\/bitcerts<\/pre>\n<h2>Starting the Maintenance Container<\/h2>\n<p>Now your website is going to need a little downtime. Fortunately this doesn&#8217;t take long, and the certificates only need to be renewed once every six months &#8211; so this outage shouldn&#8217;t have too much impact on your site availability. If your site is critical then consider using a load balancer like Amazon&#8217;s excellent Elastic Load Balancer so that you can failover to the &#8216;B&#8217; side during certificate updates.<\/p>\n<pre>cd ~\/lemp-compose\r\ndocker-compose stop\r\ncd ~\/maint-compose\r\ndocker-compose up -d<\/pre>\n<h2>Generating Certificates<\/h2>\n<p>Now your website is down you&#8217;ll want to carry out these next steps as quickly as possible in order to ensure that there&#8217;s minimal outage. In fact, it should be fairly simple to refactor these steps and script them so that the outage is limited to a few seconds &#8211; but I&#8217;ll leave that as an exercise for you.<\/p>\n<p>Log in to the maintenance container as follows:<\/p>\n<pre>docker exec -i -t --user root maintcompose_nginx_1 bash<\/pre>\n<p>Now execute the following commands in the container:<\/p>\n<pre>install_packages wget xz-utils\r\ncd \/tmp\r\ncurl -s https:\/\/api.github.com\/repos\/xenolf\/lego\/releases\/latest | grep browser_download_url | grep linux_amd64 | cut -d '\"' -f 4 | wget -i -\r\ntar xf lego_linux_amd64.tar.xz\r\nmv lego_linux_amd64 \/usr\/local\/bin\/lego<\/pre>\n<p>Generate the certificate for your website, noting that you can put in as many domain parameters as you have domains to register.<\/p>\n<pre>lego --email=\"&lt;website owner email address&gt;\" --domains=\"website domain name\" --path=\"\/etc\/lego\" run<\/pre>\n<p>Provided that no errors have been raised your certificates will now have been generated. Copy them out of the container.<\/p>\n<pre>cp \/etc\/lego\/certificates\/*.key \/bitcerts\r\ncp \/etc\/lego\/certificates\/*.crt \/bitcerts<\/pre>\n<p>Now exit out of your maintenance container and shut it down.<\/p>\n<pre>exit\r\ndocker-compose stop<\/pre>\n<h2>Configuration Updates<\/h2>\n<p>If this is the first time you&#8217;ve enabled ssl on your lemp stack you&#8217;ll need to make a few minor changes.<\/p>\n<p>First, edit your docker-compose.yml file so that you can access your new certificates. Add this volume line to the nginx section:<\/p>\n<pre>- .\/certificates:\/bitnami\/nginx\/conf\/bitnami\/certs<\/pre>\n<p>Your docker-compose.yml file should now look something like this:<\/p>\n<pre>version: '2'\r\n\r\nnetworks:\r\n app-tier:\r\n  driver: bridge\r\n\r\nservices:\r\n maria-db:\r\n  image: 'bitnami\/mariadb:latest'\r\n  networks:\r\n   - app-tier\r\n  environment:\r\n   - MARIADB_ROOT_PASSWORD=&lt;Your Root Password Goes Here&gt;\r\n  volumes:\r\n   - .\/db-data:\/bitnami\r\n nginx:\r\n  image: 'bitnami\/nginx:latest'\r\n  depends_on:\r\n   - phpfpm-&lt;your website name&gt;\r\n  networks:\r\n   - app-tier\r\n  ports:\r\n   - '80:8080'\r\n   - '8080:8080'\r\n   - '443:8443'\r\n   - '8443:8443'\r\n  volumes:\r\n   - .\/nginx\/nginx.conf:\/bitnami\/nginx\/conf\/nginx.conf\r\n   - .\/nginx\/fastcgi.conf:\/bitnami\/nginx\/conf\/fastcgi.conf\r\n   - .\/logs:\/opt\/bitnami\/nginx\/logs\r\n   - .\/nginx\/vhosts:\/bitnami\/nginx\/conf\/vhosts\r\n   - .\/public:\/opt\/bitnami\/nginx\/html\r\n   - .\/certificates:\/bitnami\/nginx\/conf\/bitnami\/certs\r\n phpfpm-&lt;your website name&gt;:\r\n  image: 'bitnami\/php-fpm:latest'\r\n  networks:\r\n   - app-tier\r\n  volumes:\r\n   - .\/public\/&lt;your website name&gt;\/htdocs:\/app\r\n   - .\/logs\/&lt;your website name&gt;-php:\/opt\/bitnami\/php\/log<\/pre>\n<p>Now you&#8217;ll need to make a change to the vhost configuration for your website. The changes we want to make will make nginx load the certificates and also force traffic over ssh (so that our users don&#8217;t accidentally connect unencrypted, which might introduce the risk that they enter secret information without having a secured connection).<\/p>\n<pre>nano ~\/nginx\/vhosts\/sites-available\/&lt;your website name&gt;.conf<\/pre>\n<p>Now edit your configuration to add your certificates &#8211; you do this by adding the ssl_certificate and ssl_certificate_key lines in your server block:<\/p>\n<pre>ssl_certificate \/bitnami\/nginx\/conf\/bitnami\/certs\/www.&lt;your website name&gt;.com.crt;\r\nssl_certificate_key \/bitnami\/nginx\/conf\/bitnami\/certs\/www.&lt;your website name&gt;.com.key;<\/pre>\n<p>Finally, lets force the connection onto https (i.e. let&#8217;s force it to be secure). We can do this by removing the listen directive for port 8080 (i.e. unsecured connections) so that our website only responds to ssl secured connections. We still need to respond to port 8080 though in order to ensure that the website loads on unsecured connections by automatically switching to a secure connection. Do this by adding a new server block:<\/p>\n<pre>server {\r\n listen 0.0.0.0:8080;\r\n server_name www.&lt;your website name&gt;.com;\r\n return 301 https:\/\/$server_name$request_uri;\r\n}<\/pre>\n<p>After all that your configuration should look something like this:<\/p>\n<pre>server {\r\n listen 0.0.0.0:8080;\r\n server_name www.&lt;your website name&gt;.com;\r\n return 301 https:\/\/$server_name$request_uri;\r\n}\r\n\r\nserver {\r\n listen 0.0.0.0:8443;\r\n server_name www.&lt;your website name&gt;.com;\r\n ssl_certificate \/bitnami\/nginx\/conf\/bitnami\/certs\/www.&lt;your website name&gt;.com.crt;\r\n ssl_certificate_key \/bitnami\/nginx\/conf\/bitnami\/certs\/www.&lt;your website name&gt;.com.key;\r\n server_tokens off;\r\n \r\n error_log \"\/opt\/bitnami\/nginx\/logs\/&lt;your website name&gt;-error.log\";\r\n access_log \"\/opt\/bitnami\/nginx\/logs\/&lt;your website name&gt;-access.log\";\r\n\r\nlocation \/ {\r\n try_files $uri $uri\/index.php;\r\n }\r\n\r\nlocation ~ \\.php$ {\r\n # fastcgi_pass [PHP_FPM_LINK_NAME]:9000;\r\n fastcgi_pass phpfpm-&lt;your website name&gt;:9000;\r\n fastcgi_index index.php;\r\n include fastcgi.conf;\r\n root \/app;\r\n fastcgi_intercept_errors on;\r\n }\r\n\r\nlocation ~ \\.htm$ {\r\n root \/opt\/bitnami\/nginx\/html\/&lt;your website name&gt;\/htdocs;\r\n }\r\n \r\n location ~* \\.(js|css|png|jpg|jpeg|gif|ico|dmg|zip)$ {\r\n expires max;\r\n log_not_found off;\r\n root \/opt\/bitnami\/nginx\/html\/&lt;your website name&gt;\/htdocs;\r\n }\r\n}<\/pre>\n<h2>Final touches<\/h2>\n<p>Even though those last few steps won&#8217;t be necessary for renewal, the next step definitely is. Right now, nginx won&#8217;t be able to load your certificate &#8211; it doesn&#8217;t have permission. Make good this problem as follows:<\/p>\n<pre>cd ~\/lemp-compose\/certificates\r\nchmod 664 *<\/pre>\n<h2>Restart your Containers<\/h2>\n<p>If you&#8217;ve carried out all of those steps successfully then you can restart your containers and enjoy your newly secured website &#8211; thanks to Let&#8217;s Encrypt!<\/p>\n<pre>cd ~\/lemp-compose\r\ndocker-compose up -d<\/pre>\n<p>This series wouldn&#8217;t have been possible without <a href=\"https:\/\/bitnami.com\">Bitnami<\/a> and <a href=\"https:\/\/letsencrypt.org\">Let&#8217;s Encrypt<\/a>, let alone <a href=\"https:\/\/nginx.org\">nginx<\/a>, <a href=\"https:\/\/mariadb.com\">MariaDB<\/a> and <a href=\"https:\/\/php-fpm.org\">PHP-FPM<\/a>. I&#8217;m haven&#8217;t been sponsored by them to write these articles &#8211; I&#8217;ve just found them to be genuinely the best solution for hosting my websites.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>If you&#8217;ve been following along with this series of tutorials, you&#8217;ll have built a LEMP stack capable of handling multiple vhosts. That&#8217;s all very well &#8211; but if you expect your users to enter their username and password into the site then you&#8217;ll need to provide them with a little security. The only way to &hellip; <\/p>\n<p class=\"link-more\"><a href=\"https:\/\/www.45rpmsoftware.com\/blog\/?p=609\" class=\"more-link\">Continue reading<span class=\"screen-reader-text\"> &#8220;Docker Lab \u2013 Containerising your website, Part 4 (Let&#8217;s Encrypt)&#8221;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":[],"categories":[15,18,23,28,5],"tags":[],"_links":{"self":[{"href":"https:\/\/www.45rpmsoftware.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/609"}],"collection":[{"href":"https:\/\/www.45rpmsoftware.com\/blog\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.45rpmsoftware.com\/blog\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.45rpmsoftware.com\/blog\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.45rpmsoftware.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=609"}],"version-history":[{"count":0,"href":"https:\/\/www.45rpmsoftware.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/609\/revisions"}],"wp:attachment":[{"href":"https:\/\/www.45rpmsoftware.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=609"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.45rpmsoftware.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=609"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.45rpmsoftware.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=609"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}