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.1binding - 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 inHow It Works
When a Lightning wallet signs the LNURL-auth challenge, the bridge:
- Verifies the signature and extracts the public key
- Creates a Ghost member via the Admin API with a synthetic email (
ln_{pubkey}@pub.txid.uk) - Requests a one-time sign-in URL from Ghost
- 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.