A minimal, production-ready setup for self-hosting Ghost in Docker behind Traefik with automatic TLS via Cloudflare DNS-01 challenges.
- Ghost 5 (Alpine) — the blog platform
- MySQL 8 — Ghost's recommended database for production
- Traefik — reverse proxy, TLS termination, HTTP→HTTPS redirect
- Cloudflare — DNS provider / wildcard cert resolver
- A VPS with Docker and Docker Compose installed
- Traefik already running and attached to an external Docker network (e.g.
traefik-network) - A domain with DNS managed by Cloudflare
- A Cloudflare API token configured as your Traefik
certresolver - A DNS A record pointing
ghost.yourdomain.comto your VPS IP
1. Clone the repo
git clone https://github.com/yourusername/ghost-docker-traefik.git
cd ghost-docker-traefik2. Create your .env file
cp .env.example .envEdit .env and fill in:
MYSQL_ROOT_PASSWORDandMYSQL_PASSWORD— generate withopenssl rand -hex 32SMTP_HOST,SMTP_PORT,MAIL_FROM— point at your mail relaySMTP_USER/SMTP_PASS— leave blank if your relay uses IP allowlisting
3. Update docker-compose.yml
Replace all occurrences of yourdomain.com and ghost.yourdomain.com with your actual domain and subdomain:
sed -i 's/yourdomain.com/example.com/g' docker-compose.yml
sed -i 's/ghost.yourdomain.com/ghost.example.com/g' docker-compose.ymlAlso update the url environment variable under the ghost service to match.
4. Update the Traefik network name
If your Traefik external network is named something other than traefik-network, update it in the networks section at the bottom of docker-compose.yml.
5. Deploy
docker compose up -d
docker compose logs ghost -fWait for:
Ghost is running in production...
Your site is now available on https://ghost.yourdomain.com/
6. Create your admin account
Go to https://ghost.yourdomain.com/ghost and complete the setup wizard. Do this immediately — the wizard is open to anyone until finished.
Internet
│
▼
Traefik (:443)
│ TLS terminated, cert via Cloudflare DNS-01
▼
Ghost container (:2368)
│ traefik-network (external overlay)
▼
MySQL container (:3306)
│ ghost-internal network (internal — not reachable from outside)
- MySQL is on
ghost-internal, which hasinternal: true— it is not reachable from Traefik or the internet, only from the Ghost container. - The
traefik.docker.network=traefik-networklabel tells Traefik which network interface to use when the Ghost container is on multiple networks. Without it, Traefik may try to route via the internal network and get a 504 gateway timeout.
Ghost uses SMTP for member notifications, newsletters, and password resets. The .env.example assumes a self-hosted relay on port 587 with STARTTLS. If your relay uses IP allowlisting rather than credentials, leave SMTP_USER and SMTP_PASS blank.
| Volume | Contents |
|---|---|
ghost-content |
Themes, images, and uploaded files |
ghost-db-data |
MySQL data directory |
Both are named Docker volumes — they persist across docker compose down and container rebuilds. To wipe and start fresh: docker compose down -v.
Ghost ships with the Source theme by default. To install the official Solo theme:
# Download on your server
curl -L https://github.com/TryGhost/Solo/releases/latest/download/solo.zip -o solo.zip
# Copy into the Ghost content volume
docker cp solo.zip ghost:/var/lib/ghost/content/themes/solo.zip
docker exec ghost sh -c "cd /var/lib/ghost/content/themes && unzip -o solo.zip -d solo && rm solo.zip"
docker restart ghostThen activate it in Ghost Admin → Settings → Design → Change theme.
MIT