A hopefully simple and cost-effective way to host a Ghost blog yourself πŸ‘» πŸš€

A small guide on how to host your own ghost blog. Bring your own VPS.

A hopefully simple and cost-effective way to host a Ghost blog yourself πŸ‘» πŸš€

I was thinking to start a blog for a while now. Every time I went on with the endeavour, I was put off either by the cost or the involvement needed to set it up yourself.

Ghost has a paid hosted version, which seems cool, but I didn't want to commit to a monthly fee since it's just an experiment.

Except for the hosted version, there are various ways to install Ghost, but I didn't want to set up many services. There is a DigitalOcean Ghost template which is probably great for most cases. But, I already had a virtual private server (VPS) running on Hetzner Cloud, so I wanted to use that one instead.

I don't think this solution will scale well if your blog starts receiving a bit more traffic. But probably for playing around, it's perfectly fine. If you reach the point that you receive some traffic and you need to scale up, then congrats πŸŽ‰. That's a great problem to have 😊

Things you need

  • a domain name and some DNS management. CloudFlare is quite nice for the DNS stuff, and you get other cute features for free, like caching and more.
  • a virtual private server (VPS).
  • Some coffee β˜•οΈ or tea 🍡 to keep you going.

Once you have the above, you can follow your registrar and VPS provider's guides for setting things up. Your end game should look something like this:

  • The VPS with a Linux distribution up and running. You know its IP address, and you have SSH access to it.
  • The domain is registered, and you create a DNS entry for the domain (or subdomain) pointing to the IP address of the VPS.

If you decide to use CloudFlare, you provide CloudFlare's DNS servers in your registrar, and you then do the DNS management on CloudFlare.

You will also need to install the following software on your VPS:

  • nginx: for SSL termination and the likes.
  • certbot: for issuing SSL Certificates with Let's Encrypt.
  • docker: for running the Ghost service.

Install services

You can follow the excellent guides of DigitalOcean for setting up Nginx:

How To Install Nginx on Ubuntu 20.04 | DigitalOcean
Nginx is one of the most popular web servers in the world and is responsible for hosting some of the largest and highest-traffic sites on the internet. In this guide, we’ll discuss how to get Nginx installed on your Ubuntu 20.04 server.

...and then setting up Let's Encrypt:

How To Secure Nginx with Let’s Encrypt on Ubuntu 20.04 | DigitalOcean
Let’s Encrypt is a Certificate Authority (CA) that provides an easy way to obtain and install free TLS/SSL certificates, thereby enabling encrypted HTTPS on web servers. It simplifies the process by providing a software client, Certbot, that attempts

Once you finish with the steps above, then you should be able to access a sample HTML under your HTTPS domain.

Finally, set up Docker:

How To Install and Use Docker on Ubuntu 18.04 | DigitalOcean
Docker is an application that simplifies the process of managing application processes in containers. In this tutorial, you’ll install and use Docker Community Edition (CE) on Ubuntu 18.04. You’ll install Docker itself, work with containers and images

Running Ghost with Docker

Ghost has a couple of dependencies, and by default, relies on an external database for storing the content. But when using Docker, you can avoid installing the dependencies and the need for an external database. I guess the internal sqlite can't scale so well, but it should be perfectly fine for our small blog, living at the edge of the internets.

As we see in the documentation of the Ghost image on Docker Hub, we can do something like this:

docker run -d --name some-ghost -p 3001:2368 -v /path/to/ghost/blog:/var/lib/ghost/content ghost:alpine

What's interesting here is the mounting of the directory which Ghost uses for storing all the data of your blog to an external place to which you can have access.

So, go on and create a directory for storing Ghost's data under something like /home/georgios/ghost, and then update the docker command to the following:

docker run -d --restart unless-stopped --name ghost -p 3001:2368 -v /home/georgios/ghost:/var/lib/ghost/content -e url=https://shoebox.nuc.gr ghost:alpine

Important parts here:

  • --restart unless-stopped : will keep the container up and running, even after reboots.
  • -p 3001:2368: mapping the 2368 internal port to be accessible under 3001.
  • -e url=https://shoebox.nuc.gr: provide the blog's URL as an environment variable, to be used as the base URL for all the links inside your blog.

Note to self: we should probably use a specific major version of the Ghost image to avoid unexpected issues when there's a new major release.

Update Nginx configuration

Once the service is up, we need to update the Nginx configuration to serve the blog from the internal 3001 port.

If you followed the DigitalOcean guides, you should have in the end an Nginx config file under this path: /etc/nginx/sites-available/example.com.

Edit it and add the following config inside the server block:

server {
  # Don't replace the rest of the config inside server,
  # since that's needed for the SSL configuration.

  # Allow file uploads up to 20MB
  client_max_body_size 20M;

  # Proxy any requests to the local ghost instance
  # running under port 3001.
  location / {
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_pass http://localhost:3001;
  }
}

Restart Nginx with something like service nginx restart and your blog should now be accessible πŸŽ‰

Backups

If your VPS has some cost-effective automated backups, then that should be fine. But if you don't want to pay extra for that, you could misuse something like GitHub and cron to back up the blog.

The nice thing using sqlite is that the single database file can be indexed and pushed to GitHub.

Quite hacky, but it works fine:

*/10 * * * * cd /home/georgios/ghost && git add . && if git diff -q; then git commit -m "Backup" && git push; fi

The end

If this guide helped you in anyway, then that's great! πŸ₯³ Feel free to reach out if you have any questions or you need any help.

Cheers β˜•οΈ