This project is designed to use Ghost as a nice usable frontend for editing multiple static websites, which don't require the subscriber management and other dynamic features that Ghost provides, by hosting Ghost for multiple of these websites on a single machine, and then publishing the generated content to static files that can be deployed elsewhere.
Ghost itself only supports running a single site on a host, so Docker containers are used for the different Ghost sites. Caddy is used as a front-end server that also generates SSL certificates for each domain.
Many ghosts need static hosts
With reasonable performance
Take those ghosts and make them toast
To serve up to your audience
This guide will walk you through setting up and managing multiple Ghost websites on a single host using Docker Compose and Caddy.
-
Clone the repository:
git clone https://github.com/inconceivableza/ghosts-toaster.git cd ghosts-toaster -
Create environment file from example:
cp ghosts-toaster.env.example .env
Edit the
.envfile to set your global configuration, mail settings, etc. The setup script will automatically generate a mysql password and web token. By default, it's set to serve up the dynamic sites atghost.$SITE_DOMAINand the static sites atwww.$SITE_DOMAINIf you prefer to serve the static sites via a CDN etc, then adjust the prefixes in this file. There are also options for how frequently watchtower should update the software, and where to send its email updates. -
Run the setup script:
./setup.sh
This will start the Docker containers.but no sites as yet will be available until you create them.
Before adding a site, set up the DNS for the site domain. This will prevent delays with Caddy using Let's Encrypt to issue SSL certificates that will happen if you only set it up later.
The GHOST_PREFIX and STATIC_PREFIX subdomains should be set up in your DNS records
to point to the ghosts-toaster server (usually with a CNAME alias record),
so that Caddy can successfully issue certificates for them.
You may well decide to serve up the static site from a different provider,
under a different subdomain. For example, you could set STATIC_PREFIX to toast
so that you can test the static site hosted by ghosts-toaster,
but set up the DNS for www.${SITE_DOMAIN} and ${SITE_DOMAIN} to serve them from a CDN.
Use the provided script:
./scripts/create-site.sh <site_name> <domain>
# Example: ./scripts/create-site.sh myblog myblog.com
# Apply changes
docker compose up -dAfter your site is running, follow the instructions provided by the script to:
- Set up the webhook for automatic static site generation
- Configure the GitHub repository for the static site
Ghosts-Toaster includes a webhook system that automatically rebuilds the static site when content changes:
- Each site uses a webhook that triggers on the
site.changedevent - The webhook notifies a webhook receiver container running internally in the Docker network
- The webhook receiver triggers the static site generator for the specific site
- Changes are automatically committed to Git and pushed to GitHub
The system handles concurrent updates intelligently:
- If a site generation is already running when a new update arrives, the update is queued
- If multiple updates arrive while a generation is running, they are combined into a single update
- This ensures efficient processing without unnecessary duplicate builds
Each static site is automatically managed in its own Git repository:
- A Git repository is initialized in the static site directory
- All changes are automatically committed with timestamped messages
- If a remote is configured, changes are automatically pushed
To set up GitHub integration for your static site:
- Create a new repository on GitHub named after your domain
- Follow the instructions provided after site creation to configure the remote
- This uses a deploy key rather than using your personal SSH keys to facilitate pushing commits. Each repository requires a unique deploy key, so custom ssh config is used to associate fictional hostnames with the ssh keys.
- An alternative is to set up a GitHub App, but that is not directly supported by this tool.
- Consider setting up GitHub Pages, Netlify or CloudFlare to host your static site directly from the repository
Where ghosts-toaster serves your sites will depend on your settings for
GHOST_PREFIX and STATIC_PREFIX. The defaults are ghost and www.
(See the above information on setting up DNS).
- To access the dynamic ghost site, including the Ghost management interface, go to
https://${GHOST_PREFIX}.${SITE_DOMAIN}, for examplehttps://ghost.example.com/for the home page, orhttps://ghost.example.com/ghost/for the management interface. - To access the static version of your site, go to
https://${STATIC_PREFIX}.${SITE_DOMAIN}, for examplehttps://www.example.com/. Note that the/ghost/management interface is not present on this version. - By default, the plain domain name at
https://${SITE_DOMAIN}/will serve the Ghost management interface under/ghost/but otherwise serve the static site.
Note that Ghost itself will be configured to have the dynamic ghost site url, so that it doesn't try and serve images etc from the other site.
Edit the site-template.yml file to update the Ghost image version, then run:
./scripts/generate-site-config.sh
docker compose up -dIf needed, you can manually trigger static site generation:
./scripts/generate-static-sites.shUse the provided backup script:
./scripts/backup-ghosts.shThis will create backups of all MySQL databases and Ghost content volumes in a dated directory under backups/.
Off-site backup is not managed by this system, but could be added to it.
To automate backups, set up a cron job:
# Backup all sites daily at 2 AM
0 2 * * * /path/to/your/project/scripts/backup-ghosts.shCheck container status and logs:
docker compose ps
docker compose logs caddy
docker compose logs ghost_mysiteCheck the webhook receiver and static generator logs:
docker compose logs webhook-receiver
docker compose logs static-generatorVerify the webhook is properly configured in Ghost admin panel (Settings > Integrations).
If commits or pushes are failing:
cd static/yourdomain.com
git status
git remote -vEnsure the remote is properly configured and you have the necessary permissions.
Add a volume mount in the site-template.yml file:
volumes:
- ghost_content_${SITE_NAME}:/var/lib/ghost/content
- ./sites/${SITE_DOMAIN}/themes:/var/lib/ghost/content/themesAll sites share the same email configuration defined in the global .env file:
# Shared mail configuration
MAIL_TRANSPORT=SMTP
MAIL_SERVICE=Mailgun
[email protected]
MAIL_PASSWORD=your_mail_passwordGhost uses this configuration for transactional email (user sign-up etc; obviously how this works in a static site may be different).
For sending newsletters, it uses a different email setup with the Mailgun API; if you want to use this, it must be configured manually for each site in the Ghost settings.
- Use strong passwords (automatically handled by the site creation script)
- Set a secure webhook secret in
.env - Keep all containers updated (watchtower is configured to do this)
- Regularly back up your data
- Use a firewall to restrict access to necessary ports only
The static site generation will impose a greater load each time something changes in the site, but allows for much more efficient serving of the site, particularly if there is high load (as well as serving from a different host or CDN).
See these references for how many ghost instances could be served from a single server:
- Multiple Ghost Instances on Digital Ocean Droplet
- Filling up a 1GB server with a stack of Ghost blogs which estimates 65MB RAM per Ghost container, with a base of 150MB (I haven't checked this)
Thanks to Joel Duncan for Host Multiple Ghost Instances with Docker
The project is licensed under the MIT license