Ghost on Docker with Traefik

1_xBc-Rxuwe6S-Pz4qclrw-A.png

In this Article, I will show you how to host your own Ghost Instance with Docker and use Traefik as a Reverse Proxy.

So let’s start with the things we need:

  • Some kind of server where everything will be deployed. I took a Hetzner Cloud Instance with 2 vCPUs and 2 GB RAM running Ubuntu 20.04
  • In addition, I have a DNS Record for blog.niecke-it.de pointing to my cloud instance

We begin by setting up docker on our server, which is explained here on their page. It is fairly simple, so I will just list the commands that need to be executed.

                # uninstall old versions 
sudo apt-get remove docker docker-engine docker.io containerd runc 
# update and install some dependencies 
sudo apt-get update 
sudo apt-get install ca-certificates curl gnupg lsb-release 
# add the GPG key from the docker repo 
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg — dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg 
# setup the apt repo 
echo \ “deb [arch=$(dpkg — print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu \
  $(lsb_release -cs) stable” | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null 
# update and install the Docker Engine 
sudo apt-get update 
sudo apt-get install docker-ce docker-ce-cli containerd.io 
# install docker-compose 
sudo apt-get install docker-compose
            

After these steps, Docker is running on our server, and we installed docker-compose in addition. This is a handy tool when deploying more than one container, which we are planing to do. Now we can start with our docker-compose.yml file which will contain all the configuration for Traefik, Ghost and a Percona Database. We start with the version definition and create two networks. One for the traffic flowing between Ghost and our database, and one for the traffic between Traefik and Ghost.

                # docker-compose.yml
version: ‘3.1’ 
networks: 
  frontend_net: 
  database_net:
            

Now we can add the definition for Traefik. Our Server should accept traffic on port 80 (http) and 443 (https). Each request on port 80 will be automatically redirected to port 443 by Traefik. In the command section, we define a certificate resolver which will handle our LetsEncrypt certificates. Here we need to define an email address, and we also define the path to a file where the resolver should store its information. This is mounted to a path on our docker host, so we do not necessarily need to fetch new certificates if we deploy a new traefik version.

                # docker-compose.yml 
# … 
services: 
  reverse-proxy: 
    image: traefik:latest 
    command: 
      -"--providers.docker" 
      -"--entrypoints.web.address=:80" 
      - "--entrypoints.web.http.redirections.entryPoint.to=websecure"
      - "--entrypoints.web.http.redirections.entryPoint.scheme=https"
      - "--entrypoints.web.http.redirections.entrypoint.permanent=true” 
     - "--entrypoints.websecure.address=:443" 
     - "--certificatesresolvers.myresolver.acme.tlschallenge=true" 
     - "--certificatesresolvers.letsEncryptResolver.acme.email=someone@niecke-it.de" 
     - "--certificatesresolvers.letsEncryptResolver.acme.storage=/letsencrypt/acme.json"
    ports: 
      - "80:80"
      - "443:443"
    networks: 
      - frontend_net 
    volumes: 
      # So that Traefik can listen to the Docker events 
      - /var/run/docker.sock:/var/run/docker.sock 
      - ./letsencrypt:/letsencrypt
            

After we have configured Traefik, we can now configure Ghost. We use the Docker image, which is maintained directly by Docker. As mentioned before, we connect the ghost instance with both our networks, so that traefik can redirect traffic to ghost and ghost can access our database. An important point here is the last line, where we define the traefik.docker.network. Since Ghost is connected with two networks we have to tell Traefik with this label which is the network it should use. Traefik sees everything happening on the docker host, this means Traefik can see both networks, but it can only use the one it is connected to. Networks created by docker-compose always start with the project name. We will name our project blog, therefore the network name will be blog_frontend_net. You should also change the host from blog.niecke-it.de to whatever your domain name is.

                services:
  reverse-proxy:
    ...
ghost:
  image: ghost:latest
  networks:
    - database_net
    - frontend_net
  volumes:
    - ./content:/var/lib/ghost/content
    - ./config.production.json:/var/lib/ghost/config.development.json
  labels:
    - "traefik.enable=true"
    -"traefik.http.routers.blog.rule=Host(`blog.niecke-it.de`)"
    -"traefik.http.routers.blog.entrypoints=websecure"
    -"traefik.http.routers.blog.tls=true"
    -"traefik.http.routers.blog.tls.certresolver=letsEncryptResolver"
    -"traefik.docker.network=blog_frontend_net"
            

In addition to this, we also need a config file for ghost. We try to keep that file simple, so just set the URL and the database connection information.

                # config.production.json
{
  "url": "https://blog.niecke-it.de",
  "server": {
    "port": 2368,
    "host": "0.0.0.0"
  },
  "database": {
    "client": "mysql",
    "connection": {
    "host": "db",
    "port": 3306,
    "user": "ghost",
    "password": "ChangeMe",
    "database": "ghost"
  }
},
  "mail": {
    "transport": "Direct"
  },
  "logging": {
    "transports": [
      "file",
      "stdout"
    ]
  },
  "process": "systemd",
  "paths": {
    "contentPath": "/var/lib/ghost/content"
  }
}
            

After all, we need a database to store our data. I choose a Percona which is compatible to MySQL. Of course, you could pick a MariaDB or MySQL instead, but I like Percona.

                services:   
  reverse-proxy: 
    ...
  ghost: 
    ...
  database:
    image: percona:latest
    env_file:
      - .env
    networks:
      - database_net
    hostname: db
    volumes:
      - ./log:/var/log/mysql
      - ./lib:/var/lib/mysql
            

For our database we also need a file containing some environment variables which should not be visible for everyone since they contain passwords. This information will be used when the Percona is initialized.

                MYSQL_DATABASE=ghost 
MYSQL_USER=ghost 
MYSQL_PASSWORD=ChangeMe 
MYSQL_ROOT_PASSWORD=RealyChangeMe
            

Since all configurations are in place now, we can start the project with one simple command.

                docker-compose --project-name blog up -d
            

This might take some time because we need to fetch some docker images from the hub and extract them, but in the end you should be able to visit your Ghost instance under the domain name you have chosen. For me, it was https://blog.niecke-it.de/ If there is a warning about an insecure page, you may need to wait a few moments until Traefik had the time to fetch your SSL certificate.

After everything is set up, we want to create our first user and login with this. For creating the initial user, visit https://your.domain/ghost and fill in your information. You can also invite other users if you plan to use ghost with some colleagues.

Now we are done, and you can start writing your blog!

Conclusion

I think Ghost is a nice tool for writing blogs with an active community, but since the Docker Image is not supported, it can be sometimes tricky to configure and to debug if something is not working. For not technical users, a Ghost instance from ghost.org might be a far better choice. Above all, the fees are really reasonable when you consider how much time and nerves you can save. I just decided to host it myself, because for me, it is just another container running somewhere, and I like to play around with those tools.

What comes next?

You may have noticed that there is no Nginx proxying the requests to Ghost. This might be of interest to you if you expect much more traffic, but then it would also be a good idea to run more than one container with Ghost. Traefik as a reverse proxy does a quite good job for my use case. Furthermore, there is just a single database server, which might not be ideal. There is still work to be done which I will summarize.

  • Migrate Ghost to my Kubernetes Cluster once it is running smoothly and scale to at least 2 instances for high availability.
  • Migrate the database to a central cluster, which will be a Percona XtraDB cluster in my case.
  • And of course revise the design. (Hardest part for me 😅)

Previously posted on blog.niecke-it.de/ghost-on-docker-with-traefik/


Only registered users can post comments. Please, login or signup.

Start blogging about your favorite technologies and get more readers

Join other developers and claim your FAUN account now!

Avatar

Daniel Niecke

Founder, dynamic_qr

@danielpa_n
Co-Founder of @dynamic_qr I like hysterically grown monoliths 🧟
Stats
19

Influence

978

Total Hits

1

Posts

Discussed tools