Why Ghost Beats Substack on Your Own Terms
Ghost is an open-source publishing platform built specifically for newsletters and blogs – not a general-purpose CMS that bolted on email after the fact. It ships with a built-in subscriber management system, a clean editor, membership tiers, and native email delivery, all in one package. Running it yourself means no platform cut on paid subscriptions, no algorithm deciding who sees your content, and no terms-of-service change threatening your audience list.
The self-hosted path does require more setup than clicking “Sign up” on a hosted service, but the tradeoff is full ownership. Your content, your subscriber data, your email infrastructure – all of it lives on your server. This guide walks through a complete Ghost installation using Docker, connecting it to a domain, and wiring up transactional email so newsletters actually reach inboxes.

What You Need Before You Start
You need a Linux server with at least 1GB of RAM – a $6/month VPS from any major provider works fine. Ghost is not especially resource-hungry, but it does need enough headroom to run Node.js alongside MySQL and a reverse proxy. A clean Ubuntu 22.04 install is the most straightforward starting point. You also need a domain name pointed at your server’s IP via an A record, and that DNS change should be live before you attempt SSL setup.
Docker and Docker Compose should be installed on the server. If you want to avoid managing Ghost’s Node.js environment manually, the Docker route keeps everything contained and makes future upgrades cleaner. You will also need a transactional email provider – Mailgun, Postmark, and Amazon SES all work – because Ghost does not send email directly through its own SMTP. Relying on a server’s raw SMTP will land your newsletters in spam folders.
Installing Ghost with Docker Compose
Create a directory for Ghost and write a docker-compose.yml file inside it. The minimal setup requires two services: the Ghost container itself and a MySQL 8 container. Ghost’s official Docker image expects environment variables for the database connection, the site URL, and mail settings. Set database__client to mysql, point database__connection__host at the MySQL service name, and fill in the database name, user, and password you define in the MySQL container’s environment block.
The url variable is critical. Set it to your full domain with HTTPS – for example, https://newsletter.yourdomain.com. Ghost uses this value to generate links in emails, so a mismatch between the URL variable and your actual domain will break member confirmation emails. Map port 2368 from the Ghost container to a local port, but do not expose it directly to the internet. A reverse proxy will handle the public-facing connection.
For mail, add the SMTP variables directly in the compose file. With Mailgun, you need mail__transport set to SMTP, mail__options__host set to Mailgun’s SMTP endpoint, port 587, and your Mailgun username and password. Postmark uses the same pattern with different host values. Store credentials in a .env file in the same directory and reference them with Docker Compose’s variable substitution so your passwords are not hardcoded in the compose file itself.
Run docker compose up -d to start both containers. Ghost initializes its database on first boot, which takes about 30 seconds. You can watch the process with docker compose logs -f ghost. Once you see “Ghost boot” in the logs with no errors, the service is running on your chosen local port and ready for the reverse proxy layer.

Configuring Nginx and SSL
Nginx sits in front of Ghost and handles HTTPS termination. Install it on the host machine and create a server block for your domain. In the location block, proxy requests to http://localhost:2368 – or whatever port you mapped in the compose file. Set the standard proxy headers: X-Forwarded-For, X-Forwarded-Proto, and Host. Ghost reads X-Forwarded-Proto to confirm it is being accessed over HTTPS, which affects how it constructs internal URLs.
Use Certbot to obtain a Let’s Encrypt certificate. The certbot –nginx command will detect your server block, handle the domain validation, and rewrite the Nginx config to redirect HTTP traffic to HTTPS automatically. Certbot also installs a renewal timer, so the certificate rotates before expiry without manual intervention. Once Nginx reloads with the new config, visit your domain – you should see Ghost’s default “Coming soon” page, which confirms the stack is working end to end.
First-Time Ghost Setup and Newsletter Configuration
Navigate to yourdomain.com/ghost to reach the admin setup screen. Ghost asks for your publication name, your email address, and a password. After logging in, go to Settings – Email newsletter to verify your sending domain. Mailgun and Postmark both require DNS records – typically a DKIM TXT record and sometimes an SPF entry – to prove domain ownership and improve deliverability. Add those records in your DNS provider’s dashboard before sending any test emails.
Ghost’s membership system lives under Settings – Members. You can allow free signups, set a paid tier using Stripe, or restrict everything to invite-only. For a newsletter-first setup, enable free signups and turn on the newsletter feature in the portal settings. Ghost will automatically send a confirmation email to new subscribers using the mail credentials you configured – test this by subscribing through your own site before announcing it publicly.
The editor uses a card-based system that handles text, images, embeds, and code blocks without requiring HTML knowledge. Posts can be published to the web, sent as email newsletters, or both simultaneously. Ghost also supports email-only posts that do not appear on the public blog, which is useful for subscriber-exclusive content without creating a separate workflow. Segment sending by membership tier directly from the publish panel – free posts go to everyone, paid posts only reach paying members.

Keeping Ghost Updated and Backed Up
Updates with Docker are straightforward: pull the new Ghost image tag, stop the running container, and bring it back up. Ghost handles database migrations automatically on startup, so most version upgrades require no manual schema work. Check the Ghost changelog before jumping major versions – occasionally a release notes a required config change or a deprecated feature that affects existing setups.
Backups need to cover two things: the MySQL database and Ghost’s content directory. The content directory holds uploaded images and themes. Automate a daily mysqldump piped to a compressed file and copy both the dump and the content folder to an off-server location. Ghost’s admin panel also has a built-in export tool under Settings – Labs that exports all posts, members, and settings as a JSON file – useful for quick restores or migrating to a new server, though it does not include media files.
If your subscriber list grows and email volume starts pushing against free-tier limits on Mailgun or Postmark, the fix is the same config change that set up mail in the first place – swap the SMTP credentials and restart the container. Ghost does not lock you into any specific email provider, which means delivery infrastructure can scale independently of the publishing platform itself.





