Self-Host Ghost: Blog CMS and Newsletter Platform 2026
TL;DR
Ghost (MIT, ~46K GitHub stars, Node.js) is a modern publishing platform combining a headless CMS, newsletter, and membership/payment system. It's the fastest path to a professional publication. Substack takes 10% of revenue. Ghost self-hosted takes 0% — you bring your own email (Mailgun/Postmark/SES) and payment processor (Stripe). Ghost(Pro) starts at $9/month; self-hosted is free.
Key Takeaways
- Ghost: MIT, ~46K stars, Node.js — blog CMS + newsletter + memberships in one
- Built-in newsletter: Send via SMTP — Mailgun, Postmark, or SES
- Members and paid subscriptions: Stripe integration for paid newsletters/memberships
- Headless CMS: REST API + GraphQL for decoupled frontends
- Performance: ~100ms page loads with built-in image optimization and lazy loading
- Themes: 100+ free themes, or build your own with Handlebars
Ghost vs WordPress vs Substack
| Feature | Ghost (self-hosted) | WordPress | Substack |
|---|---|---|---|
| License | MIT | GPL 2.0 | Proprietary |
| Cost | Free (hosting + SMTP) | Free (hosting) | Free (10% cut on paid) |
| Newsletter built-in | Yes | Plugin needed | Yes |
| Memberships | Yes (Stripe) | Plugin needed | Yes (10% cut) |
| Revenue cut | 0% | 0% | 10% |
| CMS editor | Modern (Koenig) | Gutenberg | Basic |
| API-first | Yes | Yes | No |
| Setup complexity | Medium | Medium | Zero |
| GitHub Stars | ~46K | ~20K | — |
Part 1: Docker Setup
# docker-compose.yml
services:
ghost:
image: ghost:latest
container_name: ghost
restart: unless-stopped
ports:
- "2368:2368"
volumes:
- ghost_content:/var/lib/ghost/content
environment:
url: "https://blog.yourdomain.com"
database__client: mysql
database__connection__host: mysql
database__connection__user: ghost
database__connection__password: "${MYSQL_PASSWORD}"
database__connection__database: ghost
mail__transport: SMTP
mail__options__host: "smtp.mailgun.org"
mail__options__port: "587"
mail__options__auth__user: "postmaster@mg.yourdomain.com"
mail__options__auth__pass: "${MAILGUN_PASSWORD}"
mail__from: "noreply@yourdomain.com"
depends_on:
- mysql
mysql:
image: mysql:8.0
container_name: ghost-mysql
restart: unless-stopped
volumes:
- mysql_data:/var/lib/mysql
environment:
MYSQL_ROOT_PASSWORD: "${MYSQL_ROOT_PASSWORD}"
MYSQL_DATABASE: ghost
MYSQL_USER: ghost
MYSQL_PASSWORD: "${MYSQL_PASSWORD}"
volumes:
ghost_content:
mysql_data:
docker compose up -d
Visit https://blog.yourdomain.com/ghost → setup wizard.
Part 2: HTTPS with Caddy
blog.yourdomain.com {
reverse_proxy localhost:2368
}
Part 3: Initial Setup
- Visit
https://blog.yourdomain.com/ghost - Create admin account (email + password)
- Set up your publication:
- Name: "My Blog"
- Description: "Writing about tech and startups"
- Logo and cover image
Part 4: Write and Publish
New Post → Koenig Editor:
Title → Click to write
Subtitle (optional)
Body → Start writing...
Drag-and-drop images
/gallery — image gallery
/code — code block with syntax highlighting
/html — raw HTML embed
/video — video embed
/bookmark — URL card preview
/callout — highlighted callout box
Post Settings (sidebar):
- URL slug
- Publish date (schedule for later)
- Author
- Tags
- SEO: custom meta title/description
- Feature image
Part 5: Newsletter Setup
Ghost sends newsletters to your member list via SMTP:
Settings → Email newsletter:
- Confirm SMTP settings (configured in docker-compose.yml)
- From name: "Your Name"
- Reply-to: your email
Send newsletter:
- Write a post
- Toggle Email newsletter in the sidebar
- Select which members receive it: Free, Paid, All
- Publish sends the post + email to subscribers simultaneously
Part 6: Members and Paid Subscriptions
Set up free and paid memberships:
Settings → Members → Enable Members
Free tier: Anyone can sign up for email updates
Paid tier (Stripe):
- Settings → Stripe → Connect Stripe account
- Settings → Tiers → Create paid tier
- Name: "Supporter" or "Premium"
- Price: $5/month or $50/year
- Subscribers at paid tier get access to premium content
Gate content: In the post editor → Visibility:
- Public: Everyone (logged in or not)
- Members only: Free subscribers
- Paid members only: Paying subscribers
Part 7: Themes
Change your blog's appearance:
Settings → Design → Change Theme → Upload ZIP
Popular free themes:
- Casper: Default, clean and minimal
- Edition: Newsletter-focused
- Source: Developer-friendly, fast
- Download from Ghost Marketplace
Customize Without Code
Settings → Design → Site-wide:
- Navigation menu
- Colors and fonts (theme-specific)
- Homepage layout
- Sidebar widgets
Part 8: Headless CMS (API Usage)
Use Ghost as a headless CMS with your own frontend:
# Content API — public, no auth required:
curl "https://blog.yourdomain.com/ghost/api/content/posts/?key=YOUR_CONTENT_API_KEY"
# Get posts:
curl "https://blog.yourdomain.com/ghost/api/content/posts/?key=KEY&include=tags,authors&limit=10"
# Get single post:
curl "https://blog.yourdomain.com/ghost/api/content/posts/slug/my-post-slug/?key=KEY"
// Next.js example:
import GhostContentAPI from '@tryghost/content-api'
const api = new GhostContentAPI({
url: 'https://blog.yourdomain.com',
key: process.env.GHOST_CONTENT_API_KEY,
version: "v5.0"
})
export async function getPosts() {
return await api.posts.browse({
include: ['tags', 'authors'],
limit: 'all'
})
}
Part 9: Custom Domain Email
Use your own domain for newsletter sending:
Mailgun setup:
- Sign up at mailgun.com → Add sending domain
mg.yourdomain.com - Add DNS records: MX, TXT (SPF), CNAME (DKIM)
- In Ghost config:
MAILGUN_API_KEY+MAILGUN_DOMAIN
Or use Postmark for better deliverability:
environment:
mail__options__host: "smtp.postmarkapp.com"
mail__options__port: "587"
mail__options__auth__user: "${POSTMARK_API_TOKEN}"
mail__options__auth__pass: "${POSTMARK_API_TOKEN}"
Maintenance
# Update Ghost:
docker compose pull
docker compose up -d
# Backup:
# Ghost content (images, themes):
tar -czf ghost-content-$(date +%Y%m%d).tar.gz \
$(docker volume inspect ghost_ghost_content --format '{{.Mountpoint}}')
# MySQL database:
docker exec ghost-mysql mysqldump -u ghost -p${MYSQL_PASSWORD} ghost | gzip \
> ghost-db-$(date +%Y%m%d).sql.gz
# Logs:
docker compose logs -f ghost
# Ghost CLI (if needed):
docker exec ghost ghost doctor
docker exec ghost ghost update
See all open source CMS and publishing tools at OSSAlt.com/alternatives/wordpress.