Leveling Up — Deploying Ghost with Authentik & Traefik

Leveling Up — Deploying Ghost with Authentik & Traefik

After setting up the core infrastructure (Traefik and Authentik) on Debian 13, it was time to deploy our first "real" application: a Ghost blog. Here I will be documenting the progress or lack thereof in building projects.

To make it short, in this blog entry I am walking through how to I hosted a public blog at blog.qcomb.com, but locked the sensitive /ghost admin panel behind our new Authentik SSO. It sounds straightforward, but we hit a few "production" speed bumps along the way.


Challenge 1: Setting up Ghost on Docker

Ghost is incredibly fast, but it’s a bit of a "diva" when it comes to configuration. We used the official Alpine-based Docker image to keep things lightweight.

The "SQLite" Pivot

By default, if you give Ghost a production URL (https://...), it automatically expects a MySQL database and will crash if it doesn't find one. Since we wanted to keep the setup simple, we forced Ghost to stay with SQLite even in production mode using these environment variables:

- database__client=sqlite3
- database__connection__filename=/var/lib/ghost/content/data/ghost.db

Ghost relies heavily on transactional emails for "Magic Links" (login codes) and subscriptions. We integrated Mailgun via SMTP to handle this as Ghost does not integrate with anything else. I love it when my systems are opinionated... but hate it when the systems I use force me to make some decisions. Anyway, Mailgun does not have the most generous Free Tier but it'll do!


Challenge 2: Adding an "Authentik" Wall

The coolest part of this project was putting the Ghost Admin panel behind Authentik Forward Auth. We didn't want to protect the whole blog (because people need to read it!), so we used Traefik to split the traffic.

How we split the traffic:

We defined two separate "routers" in our Docker labels:

  1. Public Router: Handles everything except /ghost. This is open to the world.
  2. Admin Router: Specifically watches for the /ghost path and has a higher priority. This router is attached to an Authentik Middleware.

When I try to access the admin panel, Traefik pauses the request, asks Authentik, "Is Miguel logged in?", and only then allows me to see the Ghost login screen.


💡 Lessons Learned & "Gotchas"

1. The "Versions" vs "Content" Volume Trap

Ghost stores its system files in /var/lib/ghost/versions and its data in /var/lib/ghost/content.

  • The Mistake: We accidentally mounted our local folder to /versions, which essentially "erased" the Ghost software inside the container.
  • The Fix: Always mount your persistent data to /var/lib/ghost/content.

2. Staff Device Verification

Modern Ghost has a feature that sends an email code every time you log in from a new device. If your email isn't perfectly configured yet, you can get locked out of your own blog.

  • The Workaround: We temporarily set security__staffDeviceVerification=false in the environment variables to get inside the dashboard and fix the email settings.

3. DNS Propagation is Real

When setting up subdomains like blog. and mg., don't keep changing your config if it doesn't work immediately. Use a tool like dig , online IP checkers, or a mobile hotspot to verify if the world can see your new records.


🏁 The Result

The blog is live at https://blog.qcomb.com. It’s fast, it’s secure, and it’s a perfect example of how Traefik and Authentik can provide "Enterprise-grade" security to a simple blog.

Next up: We’re looking at building a centralized dashboard to monitor all these moving parts in one place!