What better way to start out a tech blog than by writing out my process on how I set the blog up? By day, I am an Android developer, so this was a bit out of my comfort zone.

Although I have set up a few Wordpress blogs, this was several years ago (pre-Docker), and I was never fond of the amount of effort required to make the aesthetics decent. Ghost looks nice out of the box, and they offer a (limited) selection of free themes. Also, writing articles feels very similar to writing Medium articles, which is a pleasant experience in my opinion.

To set up the blog, I knew I wanted to use Docker. As an Android developer, I don't use Docker as often as, say, some back-end developers; however, the benefits of constraining your application to a container are plentiful. In essence, when modifying with a self-contained application, I don't have to worry about impacting other running containers. Therefore, modularization and separation of concerns is a must.


  • Virtual Private Server (VPS) – I use Vultr
  • Domain name (optional, but highly recommended as you'd have to access the blog via the VPS IP address)
  • Comfortable with the command-line and SSH
  • Have Docker installed on VPS

Initial Steps

Point your domain to the VPS IP address (obtained from the VPS provider) by providing the following DNS records to your domain provider:

Type Host Value
A Record @ {vps_ip_address}
A Record www {vps_ip_address}

Define Docker Compose

In Docker, you can run commands to create, download, run Docker containers. Additionally, you can define a docker-compose.yml file in order to define multiple docker containers with their respective configurations. In my case, I used the following configuration:

version: '2'
    image: jwilder/nginx-proxy
    container_name: nginx-proxy
      - '80:80'
      - '443:443'
      - /var/run/docker.sock:/tmp/docker.sock:ro
      - /etc/nginx/vhost.d
      - /usr/share/nginx/html
      - ./docker/certs:/etc/nginx/certs:ro

    image: jrcs/letsencrypt-nginx-proxy-companion:latest
    container_name: ssl-companion
      - ./docker/certs:/etc/nginx/certs:rw
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - proxy
      - proxy

    image: ghost:2.16
    container_name: ghost-dh
      - /home/danilo/volumes/ghost-dh
      - url=https://www.danherrera.dev
      - VIRTUAL_HOST=www.danherrera.dev,danherrera.dev
      - VIRTUAL_PORT=2369
      - LETSENCRYPT_HOST=www.danherrera.dev,danherrera.dev
      - LETSENCRYPT_EMAIL=danilo@agileninja.io

This configuration defines 3 containers: nginx-proxy, ssl-companion, and ghost-dh.

The nginx-proxy container is responsible for routing traffic to the respective docker container that defines the VIRTUAL_HOST and VIRTUAL_PORT environment variables. In the above example, the danherrera container definition defines the VIRTUAL_HOST as www.danherrera.dev and danherrera.dev, meaning that when a user accesses the IP address of the VPS via one of these domains, the nginx-proxy container (running nginx) will route traffic to the ghost-dh container.

The ssl-companion container uses nginx's LetsEncrypt SSL companion to automatically provide and manage an SSL certificate to the respective docker container that defines the LETSENCRYPT_HOST and LETSENCRYPT_EMAIL environment variables. In the above example, an SSL certificate will be provided for both www.danherrera.dev and danherrera.dev.

The ghost-dh container uses the official ghost docker image (version 2.16) to define the Ghost blog.

How do you run it?

To run the docker-compose.yml configuration, execute the following from the command line from within the same directory:

docker-compose up -d

The -d flag means the command will be executed in detached mode and you are able to run other commands.

Once the containers are running, you can test that the domain is indeed pointing to your newly created Ghost blog container!

To see all containers: docker ps -a
To stop a container: docker stop {container_names}
To run a container: docker start {container_names}
To delete a container (must be stopped): docker rm {container_names}

Thanks to my colleague Neal Sanche for entertaining a discussion on Ghost and managed to help refine the configuration in this article.

Thanks for stopping by!