Build your blog with docker-compose: Nginx and Jekyll
In this tutorial I will describe how I deployed this blog using Docker, Nginx and Jekyll on Debian 9.
To build you blog you need a public IP linked to a Debian VM / server and a domain name. An easy way to get this is by using a VPS, for example Digital Ocean or Vultr. For your blog you can get a VPS with 1CPU and 1GB RAM for about 5$/month. To get a domain name you can use a domain registrar like Hostinger or GoDaddy, first year can be as cheap as 1$.
In order to acces easily our VPS we can use SSH, but for security measures I recommend changing the default SSH port - 22, disable password login and using SSH Public Key Authentication. Also I recommend setting up a firewall like ufw.
In Jalpc-docker-compose folder clone Jalpc github repo:
git clone https://github.com/jarrekk/Jalpc.git
We need to add in nginx.conf, CNAME and .config files the domain name of the blog. To do this easier I put together a short script. We can call the script like:
Next step is to obtain a Let’s Encrypt Certificate. First we will build our containers and start them, we can do this using the following commands:
docker-compose up -d--build
docker exec-it nginx bash
Last command will open a shell inside our jekyll docker container. Here we can use the register bash script to request the certificate.
You’ll be taken through a dialog asking a few questions, including your e-mail address. Success is indicated by:
- Congratulations! Your certificate and chain have been saved at:
We can check the certificates using the following command:
Once we have the certificate we can start serving our website with the new certificate. We can do this by deleting the # from lines 39, 51 and 52 from nginx-lets-encrypt/nginx.conf file. Also we need to generate our DH parameters, to do this we will exit our container shell and run the following commands as root:
openssl dhparam -out dhparams.pem 2048
We can now restart our docker-compose in order for nginx to process the changes in config file.
docker-compose up -d
Now we should be able to browse to the url and see our website up and running.
Disable directory listing If you browse to <web_url>/static you are able to view the directory listing, this is actually a vulnerability, classified by MITRE as CWE-548: Information Exposure Through Directory Listing. In order to disable directory listing we can use this Jekyll gem JekyllRedirectFrom. First edit the content of 404.html file to start with:
Add the gem in config file, install it and rebuild the website:
echo-e"gems:\n - jekyll-redirect-from">> ./_config.xml
docker exec-it-d jekyll sh -c"gem install jekyll-redirect-from"
docker exec-it-d jekyll sh -c"jekyll build"
As this is a security blog let’s make some tests to view some security aspects of our deployment.
Test SSL configuration The Nginx configuration is inspired from this StackOverflow reponse. We can test our configration using either SSLlabs or testssl.sh bash script. Using the default configuration we get a SSLlabs score of A+ but we have 90 for Key Exchange and Cipher Strength and 95 for Protocol Support. We can get a 100 for all categories by editing nginx.conf according to some of the comments in the file, however I recommend using the default configuration for compatibility reasons. When testing with SSLlabs, one thing that might catch your eye is the DNS CAA no field. You can read more about what this means on this Qualys blog post.
We can resolve this issue pretty easy by adding the following record in our Domain Name Registrer account:
After adding this record we can run again SSLlabs test and view the change
We can also test our SSL setup using testssl.sh script, let’s test our blog. We can run testssl directly from docker, changing www.example.com with our CNAME:
docker run -ti drwetter/testssl.sh https://www.example.com/
Let’s take a look at our docker deployment, we can status of CIS Docker Community Edition Benchmark v1.1.0 using Docker Bench for Security. Out of the box this script will report a bunch of warnings, however this tutorial can help us solve most of them, I recomment following it and understand what it does. Two great resources for this comes from OWASP, this cheatsheet and OWASP/Docker-Security which, at the time of writing this post, is still under development, missing some sections. I will summerize the commands I used on my Debian 9 box. For starters I stoped the service and remove everything.
1.x Host Configuration We can pass all the checks using the following: First install auditd
sudo apt-get install auditd
Add the following rules to /etc/audit/rules.d/audit.rules
-w /usr/bin/docker -p wa
-w /var/lib/docker -p wa
-w /etc/docker -p wa
-w /lib/systemd/system/docker.service -p wa
-w /lib/systemd/system/docker.socket -p wa
-w /etc/default/docker -p wa
-w /etc/docker/daemon.json -p wa
-w /usr/bin/docker-containerd -p wa
-w /usr/bin/docker-runc -p wa
Next we restart auditd service and list our new rules
systemctl restart auditd
2.x Docker daemon configuration Using the following commands we will pass all checks except 2.11 that it’s a little harder to implement. Add the following lines to /etc/docker/daemon.json
3.x Docker daemon configuration files All checks should be passed.
4.x Container Images and Build File It’s a little harder to solve because these warnings address the images we are using and we are using standard containers from nginx and jekyll. We get warnings from 4.1 that our container are running as root, however we should change the images to run as user, for example for nginx this has been done in docker-nginx-unprivileged To solve 4.5 we can enable content trust by running the following command:
We also get warnings from 4.6 that our images doesn’t has HEALTHCHECK instructions, however we implemented healthcheck in our docker-compose file so we shouls be fine.
5.x Container Runtime We get warning from 5.7 about using port 80, 443 however this should be an exception because we want to use standard http and https ports. Also warnings from 5.10, 5.11, 5.12 and 5.28 can’t be solved from docker-compose.yml because use some deploy directives. We need to deploy our containers using swarm in order to pass these checks. To pass 5.13 check we need to replace wildcard IP from docker-compose.yml with our public IP address.
7.x Docker Swarm Configuration. Every check should be passed.
After we’ve made the changes described above we can redeploy our blog. Because with the changes we isolated container with a user namespace we need to give new rights on _site and lets-encrypt-data folders from Jalpc. First we need to find UID our container uses, we can do this by running the following commands.
Let’s run docker-bench-security to see status of our deployment:
git clone https://github.com/docker/docker-bench-security.git
sudo sh docker-bench-security.sh
We should get a score of 54
Now we can customize our blog, first by editing Jalpc/_config.yml. For more$ I did the following changes, besides obvious configurations:
On every change made, to reload and view the changes we need to run:
docker exec-it-d jekyll sh -c"jekyll build"
Now everything should be good to do, most things are easily customizable, for example I changed the code block syle, I’ve made the landing page the blog instead of about me page and commented some sections. Posts are easy to write with a markdown cheat-sheet handy. Good luck at blogging quality content!