Self-hosting Ghost with Traefik (and Sendgrid for email)
I've had some folks ask for details about how I host this blog, so I'm sharing my configuration.
This Ghost blog is self-hosted on a VPS (Virtual Private Sever) using docker compose
traefik
and Sendgrid
. Traefik is a reverse proxy that I've built quite a lot of knowledge about over the last several years, and developed a bit of a pattern that I like. It handles all the SSL certs for me, and I can plug in whatever app by way of docker compose
. This article presumes that you've already got Traefik installed, and that it listens for new docker containers advertising that they exist.
You'll need three files in any directory owned by you:
- docker-compose.yaml
- .env
- config.production.json
The docker-compose.yaml
file looks like so:
services:
ghost:
#build: ./ghost/
image: ghost:5.97.3
ports:
- "2368:2368"
restart: always
volumes:
- ./content/images:/var/lib/ghost/content/images
- ./content/themes:/var/lib/ghost/content/themes
- ./content/data:/var/lib/ghost/content/data
- ./config.production.json:/var/lib/ghost/config.development.json
labels:
- "traefik.port=2368"
- "traefik.enable=true"
- "traefik.http.routers.blog.rule=Host(`blog.your-domain.com`) || Host(`blog-admin.your-domain.com`) || Host(`www.your-domain.com`) || Host(`your-domain.com`)"
- "traefik.http.routers.blog.entrypoints=websecure"
- "traefik.http.routers.blog.tls=true"
- "traefik.http.routers.blog.tls.certresolver=lets-encrypt"
- "com.centurylinklabs.watchtower.enable=true"
networks:
- internal
- web
database:
image: mariadb:11.5
restart: always
env_file:
- .env
networks:
- internal
hostname: db
volumes:
- ./log:/var/log/mysql
- ./lib:/var/lib/mysql
labels:
- "traefik.enable=false"
networks:
web:
external: true
internal:
external: false
The .env
file looks like:
MYSQL_DATABASE=ghost
MYSQL_USER=ghost
MYSQL_PASSWORD=<your-top-secret-mysql-password>
MYSQL_ROOT_PASSWORD=<your-even-more-top-secret-root-password>
The config.production.json
should be a json blob like this:
{
"url": "https://blog.your-domain.com",
"server": {
"port": 2368,
"host": "0.0.0.0"
},
"database": {
"client": "mysql",
"connection": {
"host": "db",
"port": 3306,
"user": "ghost",
"password": "<ghost-db-password-you-set-in-docker-compose>",
"database": "ghost"
}
},
"mail": {
"transport": "SMTP",
"options": {
"service": "Sendgrid",
"host": "smtp.sendgrid.net",
"port": "587",
"auth": {
"user": "apikey",
"pass": "<sendgrid-api-key>"
}
}
},
"logging": {
"transports": [
"file",
"stdout"
]
},
"process": "systemd",
"paths": {
"contentPath": "/var/lib/ghost/content"
},
"admin": {
"url": "https://blog-admin.your-domain.com"
}
}
Once you have these files in place, docker compose up -d
to bring up the container!
Login to your admin interface via https://blog-admin.your-domain.com/ghost and you are off to the races.
Good luck!