10 min read

Self-Host Ghost: Run Your Own Blog and Newsletter

Deploy Ghost with Docker and publish from a permanent Localtonet domain. Keep 100% of subscription revenue, no platform cut, no algorithm

Ghost · Publishing · Newsletter · Self-Host · 2026

Every platform that hosts your writing also sits between you and your subscribers: a cut of every paid subscription, a policy that can change overnight, and an algorithm deciding who sees your next post. Ghost is the open-source publishing platform serious independent writers, from 404 Media to Platformer, run on their own infrastructure instead, with built-in paid memberships, newsletters, and a headless API if you ever want a custom frontend. This guide covers installing Ghost with Docker, configuring the two email paths it actually needs, and publishing from a domain you own with Localtonet.

Open source, MIT licensed, 50,000+ GitHub stars Built-in Stripe memberships, no platform cut Your own domain, your own audience

Why Not Just Use Substack

Substack and Medium make starting free and starting fast, and that is exactly the trade. Substack keeps a percentage of every paid subscription on top of Stripe's own processing fee, and your entire publication lives on infrastructure you do not control, subject to policy changes you find out about after they happen.

Ghost flips that arrangement. You keep whatever Stripe's processing fee leaves you, because there is no second platform fee stacked on top, and your archive, your subscriber list, and your domain answer to you.

The writing does not change, only who gets paid for hosting it.

What Is Ghost?

Ghost is an open-source publishing platform under the MIT license, with over 50,000 GitHub stars. It combines a modern editor, built-in paid memberships through Stripe, and newsletter delivery in one tool, and its API doubles as a headless CMS if you ever want a fully custom frontend instead of a theme.

✍️ A serious writing and publishing editor Built for long-form writing first, with the newsletter and membership tooling built directly around it, not bolted on.
💳 Paid memberships via Stripe, no platform cut Ghost connects to your own Stripe account directly. Whatever Stripe's processing fee leaves you is yours, no second platform percentage on top.
📧 Real newsletter delivery, not just an RSS feed Bulk newsletter sends are built in, designed to scale to large subscriber lists through a dedicated email provider integration.
🔌 A real API, not just a blog Use it as a headless CMS behind your own custom frontend if a theme is not what you want.
Check your resources before you start

Ghost's own documentation lists 1 vCPU and 2GB of RAM as the minimum. If you expect a paid newsletter with thousands of subscribers rather than a small personal blog, plan for 2 vCPUs and 4GB instead.

Install Ghost With Docker Compose

docker-compose.yml
services:
  ghost:
    image: ghost:5-alpine
    restart: always
    ports:
      - "2368:2368"
    environment:
      database__client: mysql
      database__connection__host: db
      database__connection__user: root
      database__connection__password: your_db_password
      database__connection__database: ghost
      url: https://blog.example.com
    volumes:
      - ghost_content:/var/lib/ghost/content
    depends_on:
      - db
  db:
    image: mysql:8
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: your_db_password
      MYSQL_DATABASE: ghost
    volumes:
      - ghost_db:/var/lib/mysql
volumes:
  ghost_content:
  ghost_db:
Terminal
docker compose up -d

Open http://localhost:2368/ghost locally to complete the setup wizard and create your first admin account.

Publish From Your Own Domain With Localtonet

Ghost's own quickstart bundles a Caddy container just to get HTTPS in front of it. Since Localtonet already terminates HTTPS on a custom domain, the Compose file above skips that piece entirely.

1

Create an HTTP tunnel

Point it at 127.0.0.1:2368 and map a custom domain, such as blog.example.com.

2

Set the url config to match exactly

The url value in your Compose file must be the real public HTTPS address. Change it and recreate the Ghost container, Ghost does not pick up config changes on a simple restart.

⚠️ Most tutorials skip this step: url controls more than the address bar

Ghost's canonical links, sitemap, RSS feed, and Stripe webhook URLs are all generated from the url config value. Leave it pointing at localhost or an old domain and members can see broken links in emails, search engines index the wrong address, and Stripe webhooks silently fail to reach your instance.

Email: Two Different Jobs, Two Different Providers

Ghost splits email into two categories that are easy to conflate during setup. Transactional email, password resets, signup confirmations, works with any standard SMTP provider. Bulk newsletter delivery to your full subscriber list is built specifically around Mailgun's API, and that is the piece most self-hosted guides gloss over.

1

Configure SMTP for transactional email

Add your SMTP credentials to the environment section of your Compose file, any standard provider works here.

2

Connect Mailgun for newsletter sends

In the admin panel, go to Settings → Email newsletter → Mailgun configuration, and paste your Mailgun API key and sending domain. This is what lets a single newsletter reach a large subscriber list reliably.

Turn On Paid Memberships

In Settings → Membership, connect Stripe directly to your account. Use restricted API keys scoped to what Ghost actually needs rather than your full secret key. Ghost generates the checkout flow, the member portal, and content-gating rules from that one connection, no separate payment plugin required.

Migrating From Substack or Medium

Ghost's built-in importer, under Settings → Labs → Import, accepts the standard CSV export most platforms produce for subscriber lists. Subscriber data typically ports over cleanly, archived posts are worth a manual review afterward since formatting does not always survive the trip.

Keep It Running Permanently

Add restart: unless-stopped to every service in your Compose file so the stack survives a Docker restart on its own. To survive a full machine reboot, install the Localtonet client as a background service once with your AuthToken from Dashboard → My Tokens:

Terminal (Administrator / sudo)
sudo localtonet --install-service --authtoken YOUR_TOKEN
sudo localtonet --start-service --authtoken YOUR_TOKEN

Only tunnels that were in Start state in the dashboard come back automatically, your booking domain included.

Self-Hosted Ghost vs. Substack vs. Medium

FeatureSubstackMediumSelf-hosted Ghost
Platform cut of subscriptionsPercentage on top of Stripe feesNo direct paid subscriptions to your own accountNone, only Stripe's own processing fee
Custom domainSupportedLimitedFull control by default
Data and subscriber ownershipExportable, but hosted on their infrastructureLimited export optionsEntirely yours, in your own database
Setup effortNone, sign up and writeNone, sign up and writeAn evening of Docker and email configuration

The honest trade-off: Substack is genuinely the faster path to your first published post, there is no server to run and no email provider to configure. Ghost's advantage compounds over time, every paid subscriber you gain keeps more of what they pay, and no platform decision ever gets between you and your archive.

🛠 Tips for Running a Self-Hosted Publication

🔑 Use restricted Stripe keys, not secret keys Scope Ghost's Stripe access to only what membership billing actually needs.
💾 Back up the content volume and the database separately Themes, images, and uploads live in the content volume. Posts, members, and settings live in MySQL. Back up both.
📬 Send a real test newsletter before your first paid post Confirm Mailgun delivery works end to end while the stakes are low, not the day you gate content behind a paywall.
🔄 Pull and restart on your own schedule docker compose pull && docker compose up -d picks up new Ghost releases whenever you choose to run it, not automatically.

Troubleshooting Common Issues

SymptomLikely causeFix
Links in emails or the sitemap point to the wrong addressThe url config still reflects an old or local addressUpdate url in your Compose file to the exact custom domain and recreate the container
Newsletter never arrives to subscribersMailgun is not configured, SMTP alone does not cover bulk sendsAdd your Mailgun API key and domain under Settings → Email newsletter
Stripe checkout fails or webhooks never fireThe url config does not match what Stripe expects for webhook deliveryConfirm url is the live custom domain, then reconnect Stripe from Settings → Membership
Ghost container won't start after a config changeEnvironment variables only apply on container recreation, not a simple restartRun docker compose up -d again after any .env or Compose file change

Frequently Asked Questions

Does Ghost take a cut of my subscription revenue?

No, when self-hosted. Stripe connects directly to your own account, so you only pay Stripe's standard processing fee, there is no second platform percentage on top.

Why do I need both SMTP and Mailgun?

They handle different jobs. SMTP covers transactional email like password resets. Mailgun handles bulk newsletter delivery to your full subscriber list, which is built specifically around Mailgun's API for reliable delivery at scale.

Can I migrate my existing Substack subscribers?

Yes, Ghost's built-in importer accepts the standard CSV export most platforms provide. Subscriber lists generally port over cleanly, review archived posts afterward since formatting can shift.

Do I need to know how to code to use Ghost?

No, Ghost ships with themes and a full admin editor. Its API is there if you ever want a fully custom frontend, but it is entirely optional.

How much does self-hosting Ghost cost?

Ghost itself is free and open source. Your cost is whatever server it runs on, roughly the price of a small VPS if you are not already running suitable hardware, plus Mailgun's own pricing once your list grows past its free tier.

Does my server need to stay online for the newsletter to send?

Yes, both Ghost and the Localtonet client need to be running when you hit publish. Running both as background services keeps this from being a manual step every time.

Can I run more than one Ghost site on the same server?

Yes, run a separate Compose stack per site with its own database and content volume, then create a separate Localtonet tunnel and custom domain for each one.

Ready to Own Your Publication?

Deploy Ghost with Docker, connect Stripe and Mailgun, then publish from your own domain with Localtonet. Free to start, no credit card required.

Get Started Free →

Localtonet is a secure multi-protocol tunneling and proxy platform designed to expose localhost, devices, private services, and AI agents to the public internet supporting HTTP/HTTPS tunnels, TCP/UDP forwarding, mobile proxy infrastructure, file server publishing, latency-optimized game connectivity, and developer-ready AI agent endpoint exposure from a single unified control plane.

support