---
title: "Deploy Ghost"
description: "Substack alternative. Publishing, email newsletters and Stripe memberships"
category: "Blogs"
url: https://railway.com/deploy/ghost-cms
---

# Deploy Ghost

Substack alternative. Publishing, email newsletters and Stripe memberships

**[Deploy Ghost on Railway](https://railway.com/template/ghost-cms)**

- **Creator:** A3A
- **Category:** Blogs
- **Total deploys:** 1

## Template content

### Redis https://cdn.sanity.io/images/sy1jschh/production/0ce0bfdcfbdbf69662b1116671f97c2dd788b655-157x157.svg

- **Image:** redis:8.2
- **Start command:** `/bin/sh -c "rm -rf $RAILWAY_VOLUME_MOUNT_PATH/lost+found/ && exec docker-entrypoint.sh redis-server --requirepass $REDIS_PASSWORD --save 60 1 --dir $RAILWAY_VOLUME_MOUNT_PATH"`

### Ghost https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/png/ghost-light.png

- **Image:** ghost:6
- **Public domain:** Yes

### MySQL https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/png/mysql.png

- **Image:** mysql:8
- **Start command:** `/bin/sh -c 'M=$(cat /sys/fs/cgroup/memory.max 2>/dev/null); expr "$M" + 0 >/dev/null 2>&1 || M=2147483648; B=$(expr $M / 2); if [ $B -lt 134217728 ]; then B=134217728; fi; echo "boot: cgroup_mem=$M innodb_buffer_pool_size=$B"; exec docker-entrypoint.sh mysqld --innodb-buffer-pool-size=$B'`

## Documentation

![Ghost logo](https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTKhpjOVAu69Hb9DYsyHvh0rYcuyZor_CXfob2Hn75g_TiYpOuFdNcsZWs&s=10)

# Deploy and Host Ghost on Railway

Ghost is an open source publishing platform combining a writing-first CMS, a native email newsletter and paid memberships in one application. Writers, journalists and companies running an engineering blog use it as a self-hosted alternative to Substack, Medium and a plugin-laden WordPress install: you keep the content, the subscriber list and the payment relationship, with no revenue share on paid subscriptions.

Deploy Ghost on Railway and this template wires up the three pieces a production install needs. The Ghost container serves both the public site and the admin app on port 2368 behind Railway's TLS edge. A MySQL 8.4 service holds every post, member and setting, reached over the private network so the database has no public address. A volume at `/var/lib/ghost/content` stores uploaded images, media and themes, so files survive every redeploy, and Redis backs Ghost's asynchronous caches. Schema migrations run automatically on first boot, so the site is ready as soon as the deploy goes green.

![Ghost Railway architecture](https://res.cloudinary.com/rroe4rtk/image/upload/v1786745732/efb846e4-7fe4-486c-8228-3d6883d238d3.png)

## Getting Started with Ghost on Railway

Open the generated Railway URL and you land on the default Ghost front end with a "Coming soon" placeholder post. There are no default credentials: go to `/ghost/` and Ghost redirects you to a one-time setup screen asking for a site title, your name, an email address and a password of at least 10 characters. Submitting it creates the site owner, signs you in and opens the Analytics dashboard.

Publish a real post next. Click **Posts → New post**, add a title and a paragraph, then use **Add feature image** to upload a picture — the best single check that the deployment is healthy, because it exercises the volume, the image resizer and the database at once. Publish, then open your site root: the post should appear in the latest-posts list, at its own slug, in `/rss/` and in `/sitemap-posts.xml`. If the image renders publicly, the volume is mounted correctly.

Then configure email before inviting anyone. Ghost's default transport delivers mail straight to recipient servers, which does not work from Railway, and Ghost 6 emails staff a confirmation code when they sign in from a new device. Your first session lasts 180 days, so the site keeps working — but set the SMTP variables below, using Mailgun, Postmark or SES, before it expires, or you cannot sign in from a second browser.

![Ghost publication homepage listing the latest published post](https://res.cloudinary.com/rroe4rtk/image/upload/v1786735137/ghost-latest-posts-homepage.png)
![Ghost post page with its uploaded feature image](https://res.cloudinary.com/rroe4rtk/image/upload/v1786735138/ghost-published-post.png)
![Ghost admin posts list showing a published article](https://res.cloudinary.com/rroe4rtk/image/upload/v1786735139/ghost-admin-posts-list.png)

## About Hosting Ghost

Ghost is a Node.js application that owns both a database schema and a content directory. Self-host it when you want the subscriber list and payment relationship in your own infrastructure, when you need themes or routes a hosted plan restricts, or when you want to drive the site headlessly.

- Writing-first editor with cards for images, galleries, embeds and code
- Native newsletters to segmented member lists, with open/click analytics
- Paid memberships through a direct Stripe connection, with no platform fee
- Handlebars themes, custom routing via `routes.yaml`, theme upload from the admin
- Content API and Admin API with key-based auth, plus webhooks

The **Ghost** service is the whole application — public site, admin, API and background jobs in one process. **MySQL** is the system of record for posts, members and settings. The **volume** holds everything that is a file rather than a row. **Redis** caches values Ghost reads asynchronously, such as remote image dimensions.

## Why Deploy Ghost on Railway

Railway removes the server administration self-hosting Ghost normally implies:

- TLS, HTTP/2 and a custom domain, with no nginx to configure
- Private networking to MySQL, so the database stays unexposed
- A volume that survives every redeploy
- Vertical scaling by slider as your audience grows
- Logs, metrics and one-click rollback

## Common Use Cases

- A company engineering blog or changelog, with invite-only staff accounts
- A paid newsletter where Stripe subscriptions go straight to your account
- A headless backend feeding a Next.js, Astro or Hugo front end via the Content API
- A docs or community site needing custom URLs via `routes.yaml`

## Dependencies for Ghost

- **Ghost** — `docker.io/library/ghost:6`, the official Docker Library image. Serves site and admin on port 2368 and migrates the schema at boot.
- **MySQL** — `docker.io/library/mysql:8`, resolving to the MySQL 8.4 LTS line. Ghost supports only MySQL 8 in production, so the major is pinned deliberately.
- **Redis** — Railway's managed Redis, for the asynchronous cache adapters.

### Environment Variables Reference

| Variable | Purpose |
|---|---|
| `url` | Public base URL; canonical links and feed URLs derive from it |
| `database__client` | Set to `mysql`; Ghost maps this to the `mysql2` driver |
| `database__connection__*` | Host, port, user, password and database name |
| `logging__transports` | Must be `["stdout"]` or logs go to a file in-container |
| `PORT` | Port Railway routes public traffic to (2368) |
| `mail__transport` / `mail__options__*` | SMTP for newsletters, invites and sign-in codes |
| `adapters__cache__settings__adapter` | Must stay `MemoryCache`; settings are read synchronously |
| `NODE_OPTIONS` | Caps the Node heap to the container, not the host |

Ghost reads every configuration key from the environment, using a double underscore per level of nesting: `database.connection.host` becomes `database__connection__host`. Values parse as JSON where possible, which is why `["stdout"]` arrives as a real array.

### Deployment Dependencies

- Runtime: Node.js 22, bundled in the official image
- Source: [github.com/TryGhost/Ghost](https://github.com/TryGhost/Ghost)
- Images: [ghost](https://hub.docker.com/_/ghost), [mysql](https://hub.docker.com/_/mysql)
- Docs: [docs.ghost.org](https://docs.ghost.org/config/)

## Hardware Requirements for Self-Hosting Ghost

| Resource | Minimum | Recommended |
|---|---|---|
| CPU | 1 vCPU | 2 vCPU |
| RAM | 1 GB Ghost, 1 GB MySQL | 2 GB Ghost, 2 GB+ MySQL |
| Storage | 1 GB volume | 5–20 GB by media volume |
| Runtime | Node.js 22, MySQL 8 | Node.js 22, MySQL 8.4 LTS |


## Self-Hosting Ghost with Docker

The quickest local run uses SQLite — fine for evaluation, unsupported for production:

```
docker run -d --name ghost -p 2368:2368 \
  -e url=http://localhost:2368 \
  -v ghost-content:/var/lib/ghost/content \
  ghost:6
```

For production, run Ghost against MySQL 8 and keep the content directory on a named volume. The following is a Docker Compose file:

```
services:
  ghost:
    image: ghost:6
    ports: ["2368:2368"]
    volumes: ["ghost-content:/var/lib/ghost/content"]
    environment:
      url: https://example.com
      database__client: mysql
      database__connection__host: db
      database__connection__user: ghost
      database__connection__password: change-me
      database__connection__database: ghost
      logging__transports: '["stdout"]'
  db:
    image: mysql:8
    volumes: ["mysql-data:/var/lib/mysql"]
    environment:
      MYSQL_ROOT_PASSWORD: change-me-too
      MYSQL_DATABASE: ghost
      MYSQL_USER: ghost
      MYSQL_PASSWORD: change-me
volumes: { ghost-content: {}, mysql-data: {} }
```

Railway replaces the Compose file, reverse proxy and certificate renewal with this template.

## How Much Does It Cost to Self-Host Ghost?

The Ghost software is free and MIT licensed: no licence fee, and no cap on posts, members or newsletter sends. Ghost(Pro), the managed service run by the Ghost Foundation, starts at $18/month billed yearly and rises with member count. Self-hosting on Railway costs infrastructure only, a fraction of a members-based plan for a typical blog. Paid memberships need Stripe and newsletters need an SMTP provider; both bill separately.

## FAQ

**What is Ghost?**
An open source, MIT-licensed publishing platform built on Node.js. It bundles a CMS, an email newsletter system and paid memberships, and works as a website or headlessly via its Content API.

**What does this Railway template deploy?**
Three services: Ghost from the official `ghost:6` image with a volume for uploads and themes, a MySQL 8.4 database on its own volume, and Redis. Ghost is the only service with a public URL.

**Why is Redis included, and can I remove it?**
Redis backs the caches Ghost reads asynchronously — remote image dimensions and theme validation. The settings cache is read synchronously and must stay in memory, which is why `adapters__cache__settings__adapter` is `MemoryCache`. For a single-instance blog Redis is optional: unset `adapters__cache__active` and remove the service.

**How do I set up email newsletters in self-hosted Ghost?**
Set `mail__transport=SMTP`, `mail__options__host`, `mail__options__port`, `mail__options__auth__user`, `mail__options__auth__pass` and a `mail__from` address on a domain you control. Ghost also supports Mailgun's API for bulk sending, configured in Settings → Email newsletter.

**How do I add a custom domain to Ghost on Railway?**
Add the domain to the Ghost service and point DNS as instructed, then update `url` to `https://yourdomain.com`. Ghost derives canonical links, feed URLs and redirects from that variable, so it must match the domain you serve.


## Similar templates

- [Pixelfed](https://railway.com/deploy/OZSdEw) — Explore + Share beautiful photos and videos on the Fediverse
- [Hexo](https://railway.com/deploy/hexo) — Self-hosted Hexo blog with editor, comments, and S3 storage
- [NodeBB](https://railway.com/deploy/nodebb) — Node.js based forum software built for the modern web

Open this page in a browser: https://railway.com/deploy/ghost-cms
