How to Self-Host a Ghost Blog
Last Updated: 11th May 2023
What is Ghost?
"Ghost was founded in April 2013, after a very successful Kickstarter campaign to create a new platform focused solely on professional publishing. Our mission is to create the best open source tools for independent journalists and writers across the world, and have a real impact on the future of online media.
Today Ghost powers an incredible range of websites; from individual bloggers who are just getting started, to large teams of writers and editors at some of the largest organisations in the world. We've built a sustainable business around a free core application, funded by a premium platform as a service to run it on." - ghost.org/about
Ghost Pro vs Self-Hosting
Feature | Ghost Pro | Self-Hosted |
---|---|---|
Auto-Updates | ✅ | ❌ |
CDN | ✅ | ❌ |
DDoS Protection | ✅ | ❌ |
Backups | ✅ | ❌ |
Have fun tinkering | ❌ | ✅ |
Full Customisation | ❌ | ✅ |
Stress-Free | ✅ | ❌ |
Funds Ghost Development | ✅ | ❌ |
Cost | $9+ pm | VPS($2-10) |
Ghost Pro (Not affiliated)
Need a Server?
Hetzner - €20 credit on us (ref) - Free Arm instance for 4 months!
Vultr - $100 credit on us (ref) - Expires after 30 days
Prerequisites
- Setup your server
- Install Docker & docker-compose
Install
-
Create docker network:
sudo docker network create --driver=bridge --subnet=10.10.10.0/24 --gateway=10.10.10.1 dockernet
-
Create a ghost folder in your home directory:
cd
mkdir ghost
cd ghost
- Use the following docker-compose template:
nano docker-compose.yaml
version: '3.1'
services:
ghost:
image: ghost:alpine
container_name: ghost
restart: unless-stopped
environment:
# see https://docs.ghost.org/docs/config#section-running-ghost-with-config-env-variables
database__client: mysql
database__connection__host: 10.10.10.2
database__connection__user: ghost
database__connection__password: <PASSWORD-1>
database__connection__database: ghost
url: http://<SITE-URL>
volumes:
- ./data:/var/lib/ghost/content
#Be careful exposing to the internet (not required with a reverse proxy)
#ports:
# - "2368:2368"
networks:
default:
ipv4_address: 10.10.10.3
ghost-db:
container_name: ghost-db
image: mysql:8
restart: unless-stopped
environment:
MYSQL_ROOT_PASSWORD: <PASSWORD-2>
MYSQL_DATABASE: ghost
MYSQL_USER: ghost
MYSQL_PASSWORD: <PASSWORD-1>
volumes:
- ./db-data:/var/lib/mysql
networks:
default:
ipv4_address: 10.10.10.2
networks:
default:
external:
name: dockernet
- Spin it up:
sudo docker-compose up -d
Setup Reverse Proxy
- Install Caddy: cyberhost.uk/caddy-setup/
- Create Caddyfile
cd
nano Caddyfile
- Add the following template:
example.com {
encode zstd gzip
reverse_proxy 10.10.10.3:2368
header {
X-Content-Type-Options nosniff
Referrer-Policy strict-origin-when-cross-origin
Strict-Transport-Security "max-age=63072000;"
Permissions-Policy "interest-cohort=()"
X-Frame-Options SAMEORIGIN
X-XSS-Protection 1
-x-powered-by
-via
-server
}
}
-
Reload the caddy config:
caddy reload
-
Head to your Ghost Blog!
-
Create your admin account by visiting
/ghost
https://example.com/ghost