Self-hosting Ghost with Lightning Login

I wanted a blog. Not a Medium account, not a Substack with a 10% cut - a real blog that I own, running on my own server, with Lightning Login instead of email/password.

This is how I set up Ghost on an Oracle Cloud ARM64 VPS with Docker, Caddy for TLS, and a custom LNURL-auth bridge that lets anyone sign in with their Bitcoin Lightning wallet.

Why Ghost?

Ghost is an open-source publishing platform. Think WordPress but faster, cleaner, and with built-in newsletters and memberships. It runs on Node.js, uses MySQL, and has an Admin API that lets you do almost anything programmatically.

The killer feature for me: zero platform fees on paid memberships. Substack takes 10%. Ghost takes 0%.

The Setup

Server

Oracle Cloud free tier ARM64 instance. 4 cores, 24GB RAM, running Ubuntu 24.04. This machine already hosts about 10 other services (Nostr relay, Telegram bots, Matrix server, various APIs) - Ghost is just another container.

Docker Compose

Ghost and MySQL in two containers. Nothing fancy:

services:
  ghost:
    image: ghost:5-alpine
    restart: always
    ports:
      - "127.0.0.1:2368:2368"
    environment:
      url: https://pub.txid.uk
      database__client: mysql
      database__connection__host: db
    depends_on:
      db:
        condition: service_healthy

  db:
    image: mysql:8.0
    restart: always
    volumes:
      - ghost_db:/var/lib/mysql
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]

Key decisions:

  • ghost:5-alpine - lightweight image, ARM64 native support
  • MySQL 8 - Ghost only officially supports MySQL for production
  • 127.0.0.1 binding - Ghost is only accessible through Caddy, never directly
  • Health check on MySQL - Ghost waits until the DB is actually ready

Caddy Reverse Proxy

Caddy handles TLS automatically. The entire config for Ghost is two lines:

pub.txid.uk {
    reverse_proxy localhost:2368
}

That is it. Caddy gets a Let's Encrypt certificate, terminates TLS, and proxies to Ghost. No nginx config files, no certbot cron jobs.

Adding Lightning Login

Ghost supports email-based magic link login out of the box. But I wanted Lightning Login - scan a QR code with your Bitcoin wallet and you are in. No email, no password, no tracking.

I already run an LNURL-auth service (txid-auth) across my sites. The challenge was bridging it with Ghost's member system.

The Flow

User clicks "Login with Lightning"
  -> QR code modal (LNURL-auth)
  -> Wallet signs challenge
  -> txid_session cookie set
  -> Ghost Admin API: create member
  -> Ghost magic link: auto sign-in
  -> User lands on pub.txid.uk, logged in

How It Works

When a Lightning wallet signs the LNURL-auth challenge, the bridge:

  1. Verifies the signature and extracts the public key
  2. Creates a Ghost member via the Admin API with a synthetic email (ln_{pubkey}@pub.txid.uk)
  3. Requests a one-time sign-in URL from Ghost
  4. Redirects the user to that URL - Ghost sets its session cookie

The synthetic email means Ghost treats Lightning users as normal members. They can read member-only content, receive newsletters (if they later add a real email), and everything just works.

What I Learned

  • Ghost's Admin API is excellent. JWT auth, clean REST endpoints, well-documented. Creating members and generating sign-in URLs is straightforward.
  • ARM64 works perfectly. Ghost's Docker image has native ARM support. No emulation, no workarounds.
  • Caddy is underrated. Automatic HTTPS with zero config. I moved everything from nginx years ago and never looked back.
  • Code Injection is powerful. Ghost lets you inject HTML/JS into every page. I used this to add the Lightning login button without touching the theme.

Try It

You can try Lightning Login right now at pub.txid.uk/lightning-login. If you have a Lightning wallet that supports LNURL-auth (Zeus, Phoenix, Alby, etc.), you can sign in with one scan.

Both login methods work side by side - email magic link for people who prefer that, Lightning for those who want something faster and more private. Eventually I will add the ability to link both to the same account.

This blog is the experiment. Let us see what gets confirmed.