🚀 Ghost v6 Upgrade + Docker Migration: What I Learned (So You Don’t Have To)

🚀 Ghost v6 Upgrade + Docker Migration: What I Learned (So You Don’t Have To)

I’ve just finished upgrading my blog to Ghost v6 and finally moved it fully into Docker, and a few things caught me off guard that might save other self-hosters some time.

1. The database change - MariaDB → MySQL 8

Upgrading to v6 requires moving away from MariaDB. I handled this first on my CLI-managed Ghost install, and it worked - but only after one crucial step:

👉 Purge every MySQL/MariaDB package before installing MySQL 8.

I had leftover libraries and a MySQL meta-package that silently interfered with the upgrade. Once everything was removed and MySQL 8 was installed cleanly, the migration ran exactly as expected.

Where things went wrong:
When I tried to migrate this new MySQL 8 database into the Docker version, the importer kept failing. It complained about a missing table:

Table 'ghost.benefits' doesn't exist

No amount of repair/debugging seemed to fix it.

So…

2. Migrating to Docker without downtime

To keep my site online throughout, I ran both versions side-by-side:

  • the old, CLI-based Ghost
  • the new Docker stack on a different internal port

After testing uploads, routing, federation, and admin access, I switched NGINX over to the container.

Because the Docker importer refused to accept my perfectly valid MySQL 8 DB, I fell back to the reliable method:

  • exported content as JSON
  • exported images and media
  • exported members to CSV
  • imported everything cleanly into the Docker install
  • reconfigured mail, storage, integrations, etc.

Not glamorous, but 100% reliable.

P.S. If you face the “migration lock” issue, Raki’s solution here worked for me!

3. Federation - the part everyone gets stuck on

Ghost’s federation is still young, so here’s what matters:

Serve JWKS locally

If you use reverse proxies or multiple domains, make sure:

/.ghost/activitypub/v1/.well-known/jwks.json

is served directly by Ghost, not intercepted by another service.
Without a working JWKS endpoint, other servers simply can’t verify you.

Resetting ActivityPub registration

The usual fix:

Settings → Network → ActivityPub → toggle off → save → toggle on

This forces Ghost to re-register with ap.ghost.org.

But sometimes it isn’t enough

In my case, ap.ghost.org had cached an old, broken registration from before I fixed my domain setup. It wouldn’t clear, and the dashboard kept erroring.

My solution:

  • self-host the ActivityPub service
  • point Ghost at my ActivityPub container instead of Ghost’s cloud one
  • clear the cached integration entry in the database
  • let Ghost register fresh with my own endpoint

Unexpected bonus:
Self-hosting ActivityPub actually made federation faster and more reliable, since everything stays within my own stack.

4. Dockerising Ghost = massive quality-of-life upgrade

Ghost was the last non-Docker service on my servers, and everything feels smoother now:

  • updates are clean
  • federation runs in its own container
  • analytics lives in its own container
  • my NGINX proxy setup is consistent
  • backups are simpler
  • zero system-level dependency conflicts

Honestly, I should have done this ages ago.


If anyone else is mid-upgrade or stuck in federation purgatory, feel free to ask questions - I’ve just survived the full v6 gauntlet and happy to help! 😅

Laura Hargreaves

Localisation engineer, language technologist and general tinkerer with opinions. I write about tech, localisation and life on the open web — chasing internet nostalgia and genuine connections online. Cat mum ×2. 🌍💜

Laura Hargreaves