Self hosted Ghost blog

Self hosted Ghost blog
Ghost logo

Overview

There is plenty of options to start blogging on the Web. Lot of existing services allowing to blog and also some solutions to self host blog on your own. And why to self-host you can ask? Just because it's free (although has limitations comparing to paid version) and you can have full control over the setup on your sever. I recommend self-hosting option for tech-savvy people.

The nice thing about Ghost is community, not that big as WordPress but still can find some answers or ask for help on the forum https://forum.ghost.org/

LLM comparison for self hosting options:

System Type Tech Stack Best For Strengths Weaknesses
Ghost Dynamic CMS Node.js Writers, blogs, newsletters, monetization Fast, clean, built-in subscriptions & API Smaller ecosystem
WordPress Dynamic CMS PHP + MySQL Blogs → full websites & e-commerce Huge plugin/theme library, very flexible Can be heavy/bloated
Hugo Static Generator Go Developer-friendly, fast blogs Super fast, secure, no database needed No built-in dynamic features
Jekyll Static Generator Ruby GitHub Pages, dev blogs Works with GitHub Pages, Markdown + Git workflow Slower builds, dev focus
WriteFreely Lightweight CMS Go Minimalist blogs, federated writing Clean, lightweight, ActivityPub support Very limited customization
Plume Federated CMS Rust Decentralized, social publishing Federated, privacy-friendly Early stage, small ecosystem

⚡ Quick take:

  • Ghost → Best for modern blogs/newsletters with subscriptions.
  • WordPress → Best all-arounder if you want flexibility.
  • Hugo/Jekyll → Best for developers who like static, fast sites.
  • WriteFreely/Plume → Best for minimalists or Fediverse enthusiasts.

Installing

There are 2 ways of installing Ghost system on the VPS with Ubuntu. Either with Ghost CLI or Docker compose. Currently Docker compose is in "preview" but seems to be reliable and should become more mature in the future.

The Ghost CLI option seems to have multiple steps, it also requires to install stuff on the machine. I personally prefer using Docker which is more elegant, do not have to install dependencies directly on the machine and most of the time just works smooth and elegant.

How To Install Ghost With Docker (preview) - Ghost Developer Docs
Preview our new batteries-included tools for self-hosting Ghost using Docker Compose.

Prerequisites

Pre-installed on VPS

  • Ubuntu 22
  • Nginx installed and configured - will not use Caddy
  • Certbot
  • domain configured with A record to point to server

Steps

Clone repository. There will be compose.yml file which is Docker compose for setting up all needed services

git clone https://github.com/TryGhost/ghost-docker.git /opt/ghost && cd /opt/ghost

Copy example env configuration to .env file\

cp .env.example .env

setup all required envs in the file

  1. DOMAIN : this is your custom domain for your Ghost site e.g. mysite.com
  2. ADMIN_DOMAIN : a separate domain for your Ghost admin e.g. admin.mysite.com (Optional, recommended)
  3. DATABASE_ROOT_PASSWORD : generate a random password with openssl rand -hex 32
  4. DATABASE_PASSWORD : generate a random password using openssl rand -hex 32
  5. SMTP Email section : your chosen SMTP service’s configuration (See: email config docs)

In the configuration it was mentioned we use Nginx. Because of that it's better to not use the Caddy that is used with Ghost. It is totally fine to remove this section from docker-compose and just put proper proxying in the Nginx configuration.

location / {
    proxy_pass http://127.0.0.1:2368;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
}

2368 is the standard port, it should be exposed on host in the Docker compose configuration so Nginx can reach the container.

Changes into Docker compose file

Expose the Ghost service port

    ports:
      - "2368:2368"
    expose:
      - "127.0.0.1:${GHOST_PORT:-2368}:2368"

remove the Caddy service as Nginx is used

You might also encounter problem with not supported CPU architecture (CPU does not support x86-64-v2) when running MySQL 8 which is required in newer versions of Ghost. In such case it is fine to use the Oracle image version that is built for this architecture. Update db image to below

image: mysql:8.4.0-oraclelinux8

More details in

CPU does not support x86-64-v2 in the latest 8.4.0 · Issue #1055 · docker-library/mysql
The latest 8.4.0 image cannot run on older CPUs (maybe after upgrading to oracle linux 9). The only message in docker logs is Fatal glibc error: CPU does not support x86-64-v2

In the end run

docker compose pull
docker compose up -d

Services should start up in the order and Ghost should be exposed on the domain, under the domain.com/ghost where you can finish the setup. Unless you specified different admin domain. It is recommended to configure admin account before opening service to public. You can do this with port forwarding and SSH to the VPS.

Updating Ghost

https://docs.ghost.org/install/docker#future-maintenance

Read before updates if there are any breaking changes or migrations needed.

Updating is quite simple.
Assuming the Ghost was run with docker compose from cloned repository provided by Ghost team and it's under the /opt/ghost folder.

git clone https://github.com/TryGhost/ghost-docker.git /opt/ghost && cd /opt/ghost

Have to pull the changes from upstream. You might have some changes done locally to e.g. remove Caddy from docker-compose to use self-hosted Nginx. Can try simply rebase with autostashing git pull --rebase --autostash

Then run to update images and run the containers again using new images.

docker compose pull
docker compose down
docker compose up -d

Web analytics

It is not required to run the blog but very useful to observe some web analytics. The setup is well described on the Ghost page.

How To Install Ghost With Docker (preview) - Ghost Developer Docs
Preview our new batteries-included tools for self-hosting Ghost using Docker Compose.

However I did not find it that easy how to set it up. I signed up with OAuth first and tried to create the workspace and project but there was number of steps to execute on the server. The docker compose run --rm tinybird-login command tried to create new workspace but it stack, seems like the server process did not catch the workspace created on Tinybird account. I then installed the Tinybird CLI and use the command tb login to log in but tb was not recognized 🤷‍♂️
I then tried docker compose run --rm tinybird-login again and on the web page selected the workspace created before. And it somehow worked, I got message the authentication was successful.