Self-Hosting A Ghost Website
How to self-host a Ghost website using a Raspberry Pi, Docker, and Cloudflare

When using open source software, such as Ghost, self-hosting can be a cost effective alternative to a paid service. Historically, when setting up a ghost website, I follow the full install via the docs, but this time I decided to switch things up and host via Docker.
Install Ubuntu
First, you can use the Raspberry Pi Imager to install Ubuntu on an SD Card. In the process, you specify the machine name, configure SSH, and connect to the network. Then, simply plug in the SD card and you're good to go.
Install Docker
Once the server's up and running, you can SSH into it and install docker following the docs. Essentially, you only need to run a few commands:
# Add Docker's official GPG key:
sudo apt-get update
sudo apt-get install ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
# Add the repository to Apt sources:
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}") stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update
# Install the Docker packages
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
If everything worked, you should be able to test the hello-world image
sudo docker run hello-world
Finally, if you want docker to run on startup, you can run
sudo systemctl enable docker.service
sudo systemctl enable containerd.service
Setup Mailgun
Ghost requires Mailgun for sending emails and newsletters, so you can follow the documentation here for getting the basics set up.
Signing up for a flex / pay as you go account (instead of a monthly subscription) required a bit of a work around, but this post details how to accomplish it.
Create a Docker-Compose
You could run the docker image for Ghost on its own, but I decided to use a docker-compose instead, because I wanted to group multiple services together. Initially, I tried to use MySQL, but ran into an error that there was no image for the architecture.
Considering that my site is pretty small, and upgrading later should be fairly straight forward, I opted to use a SQLite database instead. My final docker-compose file looked like this:
version: '3'
services:
ghost:
image: ghost:latest
restart: always
environment:
- database__client=sqlite3
- database__connection__filename=/var/lib/ghost/content/data/ghost.db
- NODE_ENV=production
- url={Website_Url}
- mail__transport=SMTP
- mail__options__service=Mailgun
- mail__options__host=smtp.mailgun.org
- mail__options__port=25
- mail__options__secure=false
- mail__options__auth__user={Mailgun_User}
- mail__options__auth__pass={Mailgun_Password}
- mail__from={No_Reply_Email}
ports:
- "2368:2368"
volumes:
- ./content:/var/lib/ghost/content
Here, I'm
- exposing port 2368 of the container
- configuring mailgun
- using a volume to store all of the content (for easy access)
Configure Cloudflare
Before starting the container, we need a way to expose the site to the internet. To do so, I decided to use a Cloudflare Zero Trust tunnel.
From the Zero Trust -> Networks -> Tunnels
tab in Cloudflare, you can click the Create a Tunnel
button which then gives you the option to install a connector.
Once the connector's installed, you can direct a host name to a local port on the machine that has the connector installed. For example, this allows directing traffic from https://kevinwilliams.dev
to localhost:2368
without exposing or forwarding any ports on the router.
Wrapping up
Now that Mailgun and Cloudflare are setup, you can start the docker container with docker compose up
. Then, navigate to your domain, create an account, and import your content or make any configuration changes.
scp ./docker-compose.yml {user}@{ip}:{directory}