marketing-content
Ghost(Pro) logo

Should I self-host Ghost(Pro)?

YESworth it

It's the same Ghost. $18/mo buys hosting, email infrastructure and zero ops — $216/yr buys a lot of newsletters, and the software is right there.

The math (nothing hidden, including your time)

Ghost(Pro) (Starter)$18.00/mo
VPS share + storage−$1.50/mo
Your maintenance: 20 min/mo at $20/h−$6.67/mo
Net saving$9.83/mo
Setup: 50 min measured (one-time)$16.67
Break-even1.7 months
Markup Index (price ÷ real self-host cost)2.2×

Price: source, checked 2026-08-05. Inputs are stored in git; every number above is derived, never hand-written.

What you lose

  • Ghost(Pro)'s bundled newsletter deliverability — bring Mailgun for bulk sends
  • Automatic upgrades from the people who wrote it (and fund it — Pro revenue pays for the software you're running)

What you're paying Ghost(Pro) for

  • publishing/blog
  • newsletter sending
  • memberships/paid subs
  • themes
Ghost running from the boot-verified compose file
Ghost, running from the compose file on this page — screenshot from our verified instance, not marketing material.

Ranked alternatives

The same publishing platform Ghost(Pro) hosts, on your own box, with the editor, the themes and the members list intact.

TryGhost/Ghost · ★ 54,736 · last commit 0d ago · MIT

Hardware

Runs comfortably on a real VPS (2 GB+).

The tested compose file (our evidence, CI-booted)

# Ghost — the same software Ghost(Pro) hosts for $18/mo (Starter)
services:
  ghost:
    image: ghost:5-alpine
    container_name: ghost
    restart: unless-stopped
    depends_on:
      ghost-db:
        condition: service_healthy
    environment:
      url: http://localhost:2368
      # Ghost exits with cryptic "Code: -97" (EAFNOSUPPORT) where IPv6 is disabled; bind IPv4.
      server__host: 0.0.0.0
      database__client: mysql
      database__connection__host: ghost-db
      database__connection__user: ghost
      database__connection__password: ghost
      database__connection__database: ghost
    ports:
      - "2368:2368"
    volumes:
      - ghost-content:/var/lib/ghost/content
    healthcheck:
      test: ["CMD-SHELL", "wget -qO- http://localhost:2368 >/dev/null || exit 1"]
      interval: 5s
      timeout: 5s
      retries: 36
      start_period: 30s

  ghost-db:
    image: mysql:8
    restart: unless-stopped
    environment:
      MYSQL_USER: ghost
      MYSQL_PASSWORD: ghost
      MYSQL_DATABASE: ghost
      MYSQL_ROOT_PASSWORD: root-changeme # throwaway test value
    volumes:
      - ghost-db:/var/lib/mysql
    healthcheck:
      # Must probe over TCP: during first-boot initialization MySQL runs a temp server that
      # answers the *socket* ping while port 3306 is still down — a socket-based check lets
      # dependents start too early and crash-loop on ECONNREFUSED.
      test: ["CMD", "mysqladmin", "ping", "-h", "127.0.0.1", "--protocol=tcp", "-ughost", "-pghost"]
      interval: 5s
      timeout: 5s
      retries: 36
      start_period: 30s

volumes:
  ghost-content:
  ghost-db:

Want a guided install instead? caniselfhostit.com/ghost-pro has AI-agent prompts that assume a bare machine — that's their half of the stool, and it's good.

Verification

Protocol v1 · verified by zernonia on 2026-08-10 · setup measured at 50 min · AI assistant: claude-code (allowed & stated)

What broke

  • Where IPv6 is disabled Ghost dies with the deeply unhelpful 'Code: -97' — it's EAFNOSUPPORT; set server__host: 0.0.0.0
  • MySQL 8's first-boot init answers socket pings while TCP is still down, so a socket-based healthcheck releases Ghost too early into an ECONNREFUSED crash-loop — probe with --protocol=tcp
  • Ghost 5 requires MySQL 8 specifically — SQLite and MariaDB are not supported paths
Full timed log
# Timed setup log: Ghost(Pro) → ghost

**Protocol:** v1 · **Verified by:** zernonia · **Date:** 2026-08-10
**Assistant:** claude-code · **Environment:** containerized runner, 2 vCPU class, Docker 29.3 / Compose v5.1

## Timeline

| Step | Time |
|---|---|
| Read upstream docs, draft compose with healthcheck | ~16 min |
| **Boot: all services healthy in 22 s (measured, `compose up --wait`)** | — |
| Endpoint-level workflow check (login/health/API serve) + re-run from clean volumes | ~16 min |
| **Total: ~50 min** | |

Boot and endpoint checks are machine-verified; `setup_min` is the wall-clock total for this
session including authoring, diagnosis and re-runs. Endpoint-level ≠ full human UI workflow —
dispute anything that doesn't reproduce (CONTRIBUTING.md).

## What broke

- Where IPv6 is disabled Ghost dies with the deeply unhelpful 'Code: -97' — it's EAFNOSUPPORT; set server__host: 0.0.0.0
- MySQL 8's first-boot init answers socket pings while TCP is still down, so a socket-based healthcheck releases Ghost too early into an ECONNREFUSED crash-loop — probe with --protocol=tcp
- Ghost 5 requires MySQL 8 specifically — SQLite and MariaDB are not supported paths

## Verdict-relevant notes

- Memberships + Stripe work self-hosted; bulk newsletter needs a Mailgun key.

Verdict history

  • 2026-08-10: unscored → YES — Initial verdict: It's the same Ghost. $18/mo buys hosting, email infrastructure and zero ops.