I decided that it was time to create the personal project that I already had in mind for quite some time: a blog to record the interesting things I find as a software developer. More than that, I also wanted to learn something out of it, and to have some fun while doing it.

I thought Docker would be ideal to organize the services I need. Simply put, Docker is a tool that allows creating and managing applications inside containers. I ended up having three of them, one for each of the services I needed:

  • NGINX, to serve my web pages;
  • MySQL, to host my databases;
  • Ghost, to manage my content.

Table of Contents

Before starting

First I chose and bought my domain, and my VPS to host the applications. I'm using Vultr as both my VPS and DNS provider.

I obtained an SSL/TLS certificate for my blog to be accessed through HTTPS. For that purpose, I used Certbot to request a certificate from Let's Encrypt for free!

sudo apt install software-properties-common
sudo add-apt-repository ppa:certbot/certbot
sudo apt update
sudo apt install certbot
sudo certbot certonly --standalone -d blog.afmonteiro.pt

The certificate is downloaded to the folder /etc/letsencrypt/live/example.com/.

Install Docker

My VPS is running a Linux machine based on Debian, so I used apt to manage my packages, following the official Docker documentation.

  1. Install all the necessary packages:
    sudo apt install apt-transport-https ca-certificates curl software-properties-common
  2. Add Docker's GPG key to authenticate the packages:
    curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add - 
  3. Verify the key's fingerprint:
    sudo apt-key fingerprint 0EBFCD88
  4. Add the stable Docker repository:
    sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
  5. Update the package index and install Docker Community Edition:
    sudo apt-get update
    sudo apt install docker-ce
  6. Add your Linux user to the docker group:
    sudo usermod -aG docker $USER
  7. Download and install the latest version of Docker Compose, and set permissions to run it. I'm running the version 1.24.1, check the releases page for the latest version.
    sudo curl -L https://github.com/docker/compose/releases/download/1.24.1/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose
    sudo chmod +x /usr/local/bin/docker-compose

Now that Docker and Docker Compose are installed, the process can continue to its next step - installing the three main components, which will be listed in a single Docker Compose file. For those who don't know Compose, it is described as the following in the official documentation:

Compose is a tool for defining and running multi-container Docker applications. With Compose, you use a YAML file to configure your application’s services. Then, with a single command, you create and start all the services from your configuration.

Creating the Docker Compose file

  1. Create and change to the directory that will hold all the Docker services:

    mkdir docker && cd docker
  2. Create an empty file named docker-compose.yml:

    touch docker-compose.yml
  3. Open docker-compose.yml with your favorite text editor, and paste the following snippet. Replace MY_DOMAIN with your domain, and insert a new database password where MY_DB_PASSWORD appears.

    # docker/docker-compose.yml
    version: '3'
        image: ghost:latest
        restart: always
          - db
          url: MY_DOMAIN
          database__client: mysql
          database__connection__host: db
          database__connection__user: root
          database__connection__password: MY_DB_PASSWORD
          database__connection__database: ghost
          - /opt/ghost_content:/var/lib/ghost/content
        image: mysql:5.7
        restart: always
          - /opt/ghost_mysql:/var/lib/mysql
          context: ./nginx
          dockerfile: Dockerfile
        restart: always
          - ghost
          - "80:80"
          - "443:443"
           - /etc/letsencrypt/:/etc/letsencrypt/
           - /usr/share/nginx/html:/usr/share/nginx/html
  4. Docker Compose creates a few Docker bind mounts:

    • /var/lib/ghost/content and /var/lib/mysql inside the containers are mapped to /opt/ghost_content and /opt/ghost_mysql, respectively. These locations will store Ghost and MySQL data.
    • A bind mount for /etc/letsencrypt/ is created for NGINX to access the Lets Encrypt certificates.
    • NGINX also uses a bind mount for /usr/share/nginx/html so that it can access the Let’s Encrypt challenge files that are created when your certificate is renewed.

    Directories should be created if they do not exist already:

    sudo mkdir /opt/ghost_content
    sudo mkdir /opt/ghost_mysql
    sudo mkdir -p /usr/share/nginx/html

Create the NGINX Docker Image

  1. Create a new directory nginx (inside the previously created directory):
    mkdir nginx
  2. Create a file named Dockerfile and paste in the following contents:
    # nginx/Dockerfile
    FROM nginx:latest
    COPY default.conf /etc/nginx/conf.d
  3. Create a file named default.conf and paste in the following contents. Replace MY_DOMAIN with your own domain.
    # nginx/default.conf
    server {
      listen 80;
      listen [::]:80;
      server_name MY_DOMAIN;
      # Useful for Let's Encrypt
      location /.well-known/acme-challenge/ { root /usr/share/nginx/html; allow all; }
      location / { return 301 https://$host$request_uri; }
    server {
      listen 443 ssl http2;
      listen [::]:443 ssl http2;
      server_name MY_DOMAIN;
      ssl_protocols TLSv1.2;
      ssl_ciphers HIGH:!MEDIUM:!LOW:!aNULL:!NULL:!SHA;
      ssl_prefer_server_ciphers on;
      ssl_session_cache shared:SSL:10m;
      ssl_certificate     /etc/letsencrypt/live/MY_DOMAIN/fullchain.pem;
      ssl_certificate_key /etc/letsencrypt/live/MY_DOMAIN/privkey.pem;
      location / {
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-Proto https;
        proxy_pass http://ghost:2368;

Run and test the new blog

When the environment is ready, run docker-compose up -d. This creates a daemon which runs the components in the background. To stop the instances, run the command docker-compose down. These commands should be run inside the folder that contains the Docker Compose file!

The command docker-compose up can be issued without the flag -d so the logs are shown in the console, running Docker Compose in attached state, this being useful for seeing errors if one occurs. To shut down the services in this case, press the combination CTRL-C in your keyboard.

Final thoughts

Docker Compose is an ideal tool for a simple environment in which multiple applications need to be running in parallel and communicating with each other.

Creating an environment using Docker Compose may seem easy, but some unexpected errors may occur to some people. I had some trouble configuring the different components which required me to do some research on the side. Please use this guide at your own discretion, and feel free to leave me a comment if you have any doubt or suggestion.

Source: How to Install Ghost CMS with Docker Compose on Ubuntu 18.04