Blog Migration to Ghost

I may have noticed, that the design of the blog has changed dramatically. It is because I've migrated to Ghost. It is a self-hosted blogging/monetization platform, like a self-hosted medium or a substack in a lot of ways. They offer (almost) all the same features in the self-hosted version, as they do in the SaaS version, which is always great to see...
(As a side note, I hate it when a self-hosted option has stuff like OIDC SSO behind a paywall, it's so moronic).

I've tried it out using an LXC container with the Proxmox VE Scripts, but ultimately decided to put it into docker instead.

Originally, the blog was hosted using a self-hosted instance of WriteFreely. It is a very nice platform if you focus primarily on text. One of the drawbacks I had with it, was that any time I needed to add an image to the blog post, I would have to first upload it somewhere, and the youtube embeds for the podcast episodes would have to be formatted manually via an <iframe/> and they would look like ass on mobile.

Also, this migration has given me a chance to repatriate the blog to my homelab, as it's not as critical, and I want to reduce my VPS portfolio.

I've used portainer stacks to deploy this app, and it was fairly straight-forward.
I took the docker compose file from the documentation, tweaked it slightly, and simply created a new stack.

services:
  ghost:
    image: ghost:5-alpine
    restart: always
    ports:
      - 8080:2368
    environment:
      # see https://ghost.org/docs/config/#configuration-options
      url: https://blog.vasi.li/
      database__client: mysql
      database__connection__host: db
      database__connection__user: root
      database__connection__password: $MYSQL_ROOT_PASSWORD
      database__connection__database: ghost
      # mail settings
      mail__from: $EMAIL_FROM
      mail__transport: SMTP
      mail__options__host: $EMAIL_SERVER
      mail__options__port: 587
      mail__options__service: Exceede Media
      mail__options__auth__user: $EMAIL_USER
      mail__options__auth__pass: $EMAIL_PASSWORD

    volumes:
      - ghost:/var/lib/ghost/content

  db:
    image: mysql:8.0
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: $MYSQL_ROOT_PASSWORD
    volumes:
      - db:/var/lib/mysql

volumes:
  ghost:
  db:

The homelab is already set up with traefik for https fronting, so adding the domain mapping and re-pointing DNS was a matter of few minutes.

What did take considerably longer was migrating old content. Unfortunately there's no native way to migrate from WriteFreely.
I spend several days playing with lexical, which is Meta's WYSIWYG editor, which ghost uses internally. I tried going the route of dumping the database, which contains all posts in markdown format, then using marked library, which can output AST for the content, and then try to use lexical API to convert it into something ghost would be happy with, however I couldn't really get it to work.
lexical is very "build it yourself" in terms of what is renderable, and trying to use ghost's lexical package was incompatible with main lexical package, and in the end I kinda gave up and converted all the posts by hand 😢.

I was also able to wire up Mermaid, by putting this small script into the template override:

<script type="module">
  import mermaid from "https://unpkg.com/mermaid@11.6.0/dist/mermaid.esm.min.mjs";
  mermaid.initialize({startOnLoad:true});
  await mermaid.run({
    querySelector: 'code.language-mermaid',
  });
</script>

```mermaid pragma

Ghost also allows paid tiers, but considering the amount of subscribers I have it's a bit premature 🤣

Subscribe to Vasili's Blog

Don’t miss out on the latest issues. Sign up now to get access to the library of members-only issues.
jamie@example.com
Subscribe