Blog Setup: Docker, Ghost, SSL - Easy Guide

Blog Setup: Docker, Ghost, SSL - Easy Guide

January 7, 2024

blogging
ghost
docker
docker-compose
selfhosting

This week I was researching and looking for services that would help me manage my blog, while doing this research I came across Ghost, a service that despite not being the one chosen for this migration, the truth is that I liked it a lot, as I had a bit of a fight to be able to After setting it up, I decided to create this post to share the process and the resulting docker-compose.

As I told you, we are going to install Ghost using Docker, in this case we will use a docker-compose.yml file that will describe how the different services that we will require for said installation should be deployed and configured. This process involves several services such as a reverse proxy (nginx-proxy), an SSL certificate generator (letsencrypt), a database (mariadb), and Ghost itself. Here's how to configure each of these.

Before starting

In this guide I will assume that we already have docker and docker compose installed in our OS. If you do not have it installed, I attach a link to the official documentation to facilitate the process.

Reverse Proxy Configuration (nginx-proxy)

The nginx-proxy service acts as a reverse proxy for Docker containers. Facilitates access to services through hostnames and manages Nginx configuration automatically.

We will use the image: jwilder/nginx-proxy, in addition to exposing ports 80 and 443 for HTTP and HTTPS traffic, we must also mount several volumes to share Docker sockets, SSL certificates and host configurations, finally We will configure a tag to be able to integrate with the letsencrypt-nginx-proxy-companion plugin which will allow us to generate the SSL certificate for our domain, we will see this in more detail in the next step.

version: '3'
 
services:
    nginx-proxy:
        image: jwilder/nginx-proxy
        container_name: nginx-proxy
        ports:
            - '80:80'
            - '443:443'
        volumes:
            - /var/run/docker.sock:/tmp/docker.sock:ro
            - certs:/etc/nginx/certs:rw
            - vhost.d:/etc/nginx/vhost.d
            - html:/usr/share/nginx/html
        labels:
            - com.github.jrcs.letsencrypt_nginx_proxy_companion.nginx_proxy

SSL Certificate Generator (Letsencrypt)

The letsencrypt service provides automatic SSL certificates for configured domains.

We will use the image: jrcs/letsencrypt-nginx-proxy-companion, it is important to indicate that this container depends on the one configured previously: nginx-proxy, as for the volumes we will use the same ones as nginx-proxy to access the configuration and store the certificates, finally we must define the variable NGINX_PROXY_CONTAINER to indicate the dependency with nginx-proxy:

version: '3'
 
services:
    nginx-proxy:
        image: jwilder/nginx-proxy
        container_name: nginx-proxy
        ports:
            - '80:80'
            - '443:443'
        volumes:
            - /var/run/docker.sock:/tmp/docker.sock:ro
            - certs:/etc/nginx/certs:rw
            - vhost.d:/etc/nginx/vhost.d
            - html:/usr/share/nginx/html
        labels:
            - com.github.jrcs.letsencrypt_nginx_proxy_companion.nginx_proxy
 
    letsencrypt:
        image: jrcs/letsencrypt-nginx-proxy-companion
        container_name: letsencrypt
        depends_on:
            - nginx-proxy
        volumes:
            - /var/run/docker.sock:/var/run/docker.sock:ro
            - certs:/etc/nginx/certs:rw
            - vhost.d:/etc/nginx/vhost.d
            - html:/usr/share/nginx/html
        environment:
            - NGINX_PROXY_CONTAINER=nginx-proxy

Database (MariaDB)

MariaDB as the database for Ghost.

We will use the image: mariadb:latest, we must configure the credentials and name of the database and persist the data on a volume:

version: '3'
 
services:
    nginx-proxy:
        image: jwilder/nginx-proxy
        container_name: nginx-proxy
        ports:
            - '80:80'
            - '443:443'
        volumes:
            - /var/run/docker.sock:/tmp/docker.sock:ro
            - certs:/etc/nginx/certs:rw
            - vhost.d:/etc/nginx/vhost.d
            - html:/usr/share/nginx/html
        labels:
            - com.github.jrcs.letsencrypt_nginx_proxy_companion.nginx_proxy
 
    letsencrypt:
        image: jrcs/letsencrypt-nginx-proxy-companion
        container_name: letsencrypt
        depends_on:
            - nginx-proxy
        volumes:
            - /var/run/docker.sock:/var/run/docker.sock:ro
            - certs:/etc/nginx/certs:rw
            - vhost.d:/etc/nginx/vhost.d
            - html:/usr/share/nginx/html
        environment:
            - NGINX_PROXY_CONTAINER=nginx-proxy
 
    mariadb:
        image: mariadb:latest
        container_name: mariadb
        environment:
            - MYSQL_ROOT_PASSWORD=rootpassword
            - MYSQL_DATABASE=ghost
            - MYSQL_USER=ghost
            - MYSQL_PASSWORD=ghostpassword
        volumes:
            - mariadb-data:/var/lib/mysql

Ghost Service

Finally, we need to configure the Ghost service itself.

We will use the image: ghost:lates, we must indicate nginx-proxy and mariadb as dependencies to wait until we have already configured the SSL certificate and the DB before raising ghost, we must configure the following URL variables, the virtual host, the email for LetsEncrypt, and the database credentials, we need to create a new volume to store the data generated by the service:

version: '3'
 
services:
    nginx-proxy:
        image: jwilder/nginx-proxy
        container_name: nginx-proxy
        ports:
            - '80:80'
            - '443:443'
        volumes:
            - /var/run/docker.sock:/tmp/docker.sock:ro
            - certs:/etc/nginx/certs:rw
            - vhost.d:/etc/nginx/vhost.d
            - html:/usr/share/nginx/html
        labels:
            - com.github.jrcs.letsencrypt_nginx_proxy_companion.nginx_proxy
 
    letsencrypt:
        image: jrcs/letsencrypt-nginx-proxy-companion
        container_name: letsencrypt
        depends_on:
            - nginx-proxy
        volumes:
            - /var/run/docker.sock:/var/run/docker.sock:ro
            - certs:/etc/nginx/certs:rw
            - vhost.d:/etc/nginx/vhost.d
            - html:/usr/share/nginx/html
        environment:
            - NGINX_PROXY_CONTAINER=nginx-proxy
 
    mariadb:
        image: mariadb:latest
        container_name: mariadb
        environment:
            - MYSQL_ROOT_PASSWORD=rootpassword
            - MYSQL_DATABASE=ghost
            - MYSQL_USER=ghost
            - MYSQL_PASSWORD=ghostpassword
        volumes:
            - mariadb-data:/var/lib/mysql
 
    ghost:
        image: ghost:latest
        container_name: ghost
        depends_on:
            - nginx-proxy
            - mariadb
        environment:
            - url=https://example.com
            - VIRTUAL_HOST=example.com
            - LETSENCRYPT_HOST=example.com
            - LETSENCRYPT_EMAIL=example@example.com
            - database__client=mysql
            - database__connection__host=mariadb
            - database__connection__user=ghost
            - database__connection__password=ghostpassword
            - database__connection__database=ghost
        volumes:
            - ghost-data:/var/lib/ghost/content

Volumes and network

Finally, we must define the volumes for data persistence and a default bridge type network.

Final content of the compose file

Here is the complete docker-compose.yml that describes the mentioned configuration:

version: '3'
 
services:
    nginx-proxy:
        image: jwilder/nginx-proxy
        container_name: nginx-proxy
        ports:
            - '80:80'
            - '443:443'
        volumes:
            - /var/run/docker.sock:/tmp/docker.sock:ro
            - certs:/etc/nginx/certs:rw
            - vhost.d:/etc/nginx/vhost.d
            - html:/usr/share/nginx/html
        labels:
            - com.github.jrcs.letsencrypt_nginx_proxy_companion.nginx_proxy
 
    letsencrypt:
        image: jrcs/letsencrypt-nginx-proxy-companion
        container_name: letsencrypt
        depends_on:
            - nginx-proxy
        volumes:
            - /var/run/docker.sock:/var/run/docker.sock:ro
            - certs:/etc/nginx/certs:rw
            - vhost.d:/etc/nginx/vhost.d
            - html:/usr/share/nginx/html
        environment:
            - NGINX_PROXY_CONTAINER=nginx-proxy
 
    mariadb:
        image: mariadb:latest
        container_name: mariadb
        environment:
            - MYSQL_ROOT_PASSWORD=rootpassword
            - MYSQL_DATABASE=ghost
            - MYSQL_USER=ghost
            - MYSQL_PASSWORD=ghostpassword
        volumes:
            - mariadb-data:/var/lib/mysql
 
    ghost:
        image: ghost:latest
        container_name: ghost
        depends_on:
            - nginx-proxy
            - mariadb
        environment:
            - url=https://example.com
            - VIRTUAL_HOST=example.com
            - LETSENCRYPT_HOST=example.com
            - LETSENCRYPT_EMAIL=example@example.com
            - database__client=mysql
            - database__connection__host=mariadb
            - database__connection__user=ghost
            - database__connection__password=ghostpassword
            - database__connection__database=ghost
        volumes:
            - ghost-data:/var/lib/ghost/content
 
volumes:
    ghost-data:
    mariadb-data:
    certs:
    vhost.d:
    html:
networks:
    default:
        driver: bridge

Running this docker-compose.yml with the docker-compose up -d command will raise all the services needed to run Ghost in a Dockerized environment, with SSL and database configured.

You would only have to configure your blog by entering the url that you configured within the service, in my case for the example it would be the following: https://example.com/ghost


Thanks for reading me 😊