Ghost Blog with Free Webhosting

- Updated 2/21/2025
When I began my search to find the best blog/CMS that I could host at home (or in my dorm room) on my server, I landed on Ghost pretty quickly with its slick UI and broad feature-set including an admin panel, app integrations, and native Docker support.
My problems arose when it came to hosting. Although I can host it on my server I currently cannot port-forward traffic to my blog, and it also introduces a pretty significant security risk.
Bring in GitHub pages. GitHub pages is a great way to host websites completely for free with the one caveat being that they have to be completely static. As Ghost doesn't natively support this to my knowledge, this is where the guide begins.
Requirements
- Server/Laptop/PC you have to host the server on for local editing. The final site will be on GitHub pages so this machine doesn't have to be running 24/7.
- Free GitHub account
- Basic Linux command line knowledge
- Patience
Setting up Linux environment
This guide should work natively on Linux and macOS (untested) and if you only have a Windows machine, it also works using WSL2.
First, we need to install Docker and some other packages. Going forward, I will be listing commands that would be used on Debian/Ubuntu-based systems as that is what I personally used, but they should be similar for other platforms. To install Docker, we can run these commands below.
sudo apt-get install ca-certificates gnupg lsb-release nano git wget curl imagemagick optipng pngcrush jpegoptim
Download general packages and image optimization
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
Add Docker GPG key
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu \
$(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
Set up stable repository
sudo apt-get update
sudo apt-get install docker-ce docker-ce-cli docker-compose containerd.io
Next, we want to install all of the packages needed for the static site generator that we will be running outside of the Docker container on bare metal.
sudo apt-get install python3 python3-pip wget
Download Python 3 and pip
Setting up the Ghost Docker container
The method I'm going to be using to set up Ghost with Docker is through docker-compose. Firstly we need to create a docker-compose.yml file.
nano docker-compose.yaml
In nano paste and edit the docker-compose install parameters below.
Optionally you can mount the folders from within your docker container to somewhere on your local machine which can make development much easier. If you would like to do that add a volumes:
section under the environment:
section on both the ghost
and db
containers. If you are running Ghost on a remote server make sure to change url:
from http://localhost:2368
to http://SERVERIP:2368
. If you don't want to deal with any volumes you can delete both sections entirely.
You also need to know your PUID
and PGID
of your user account. Run the following commands to determine those values and then add those to the file below.
id -u # returns PUID
id -g # returns PGID
Get your PUID and PGID
version: '3.1'
services:
ghost:
image: ghost:latest
restart: always
ports:
- 2368:2368
environment:
# see https://ghost.org/docs/config/#configuration-options
database__client: mysql
database__connection__host: db
database__connection__user: root
database__connection__password: EXAMPLE
database__connection__database: ghost
PUID: 1000 # change these to your linux account with "id -u" and "id -g"
PGID: 100
url: http://localhost:2368
#NODE_ENV: development
volumes: # allows you to easier develop and transfer files in and out of your website
- /PATH/ON/LOCAL/MACHINE/ghost/content:/var/lib/ghost/content
db:
image: mysql:8.0
restart: always
environment:
MYSQL_ROOT_PASSWORD: EXAMPLE
MYSQL_DATABASE: ghost
PUID: 1000 # change these to your linux account with "id -u" and "id -g"
PGID: 100
volumes: # allows you to easier develop backup/manage your database
- /PATH/ON/LOCAL/MACHINE/ghost/mysql:/var/lib/mysql
docker-compose.yml
Press Ctrl-X
to exit and Y
to save your changes. Before we start the container, we also want to add your current user to the Docker group on your computer so you will be able to run all of these commands without using sudo.
sudo groupadd docker
Create the Docker group
sudo usermod -aG docker $USER
Add the current user to the Docker group
You will likely have to log out and log in for these changes to be implemented.
Finally, we can get into actually starting up the Ghost blog. If you are not running this on a server and are on a laptop or desktop, I recommend using VSCode with the Docker extension. The workflow is very nice for managing Docker containers and images.

Now we need to load up the Docker container we created earlier. Navigate to the directory that you created the docker-compose.yml file in and run
docker-compose up -d
This should take a while as it has to download and set up the Ghost Docker image but after it says the website is finished loading you should be able to navigate in your browser to http://localhost:2368
or http://serverip:2368
depending on your setup. You should see a nice-looking blog template with all the images loaded properly. Then you can add /ghost
to the end of your URL to access the admin dashboard.

Here it will prompt you to set up an account and parts of your blog. I'm not going to go too much more in-depth on that side of things as that is more up to the user but if you have everything up to here working we should be good to set up the GitHub pages integration.
Setting up GitHub Pages
Now that we have our local Ghost blog up and running, we want to configure a static site to be generated and sent to GitHub Pages. To do this I made a simple python script that you can use to scrape your local site and push it to GitHub.
Here we are going to create a folder to store your blog, download my script and the requirements file, and initialize a git repository.
mkdir ghost
cd ghost/
wget https://github.com/CKraft11/ghost-static-blog/blob/main/ghost_static_generator.py
git init
Next we need to set up a python virtual environment and install the dependencies for my script.
python3 -m venv ghost-static-env
source ghost-static-env/bin/activate
pip install pillow-avif-plugin requests beautifulsoup4 pillow gitpython
Then we have to go online to Github and create a few things. Starting with the repository, create a repository with any name you like and copy the URL under the "code" drop-down.

We want to add this repository as a remote repository location for git to push the files. We can do this by running
git remote add origin https://github.com/yourlinkhere.git
We need to go back to GitHub and generate a personal access token that my script can use when creating the site to push files to your repo. Go to this link here and create a new token with a name of your choice and give it repo permissions. Copy this link and keep it somewhere safe (e.g., not in your git repository folder). Next, we can run the following commands to test everything working.
touch test
git add .
git commit -m "first commit"
git push -u origin master
git config --global credential.helper store
When prompted for a username and password, use the token created earlier in place of your password.
You should see a test file in your GitHub repo and a commit message if all has gone well.
Set up Scraper Script with GitHub
Lastly, we need to set up a script to update GitHub with the latest version of the blog. Luckily, I have made a script for this which we downloaded earlier.
Be sure to change these variables in the main function with those that you're using.
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Generate static site from Ghost blog")
parser.add_argument("--force-reconvert", action="store_true", help="Force reconversion of all images")
args = parser.parse_args()
source_url = "https://cadenkraft.com" # Change this to your local Ghost URL
target_url = "https://cadenkraft.com" # Change this to your target URL
repo_path = "/home/ghost-static-site-gen" # Change this to your local repo path
generator = ImprovedGhostStaticGenerator(source_url, target_url, repo_path, force_reconvert=args.force_reconvert)
generator.run()
Open ghost_static_generator.py and change those three variables
To run the script simply run the command below. Due to the image optimization that I've implemented the first commit might take a while if you already have context on the site as all the images need to be converted. This is the command you run whenever you want to push an update from your website.
source ghost-static-env/bin/activate # Every new session you will have to run this.
python3 ghost_static_generator.py
Run python script to scrape and push changes.
Image Optimization
This script has pretty robust image optimization as that is something that ghost doesn't do too well at in my opinion. My solution is a bit overkill but should make your site load the fastest on the widest range of browsers. For every image that you upload my script will create a JXL, AVIF, and WEBP, and prioritize them as such based on what the user's browser supports.
Conclusion
If all of this went successfully, you should have many files in your GitHub repo now. To set up GitHub pages, go to the GitHub pages tab in settings and select the master branch. You can set up a custom domain here as well. Just make sure it is the one you have been using in these scripts. You can then go into the GitHub actions tab and open the static.yml
file for the default "Deploy static content to Pages" action. Under actions you can change your upload repository from the root directory to the public folder created by the script.
jobs:
# Single deploy job since we're just deploying
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Pages
uses: actions/configure-pages@v5
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
# Upload entire repository
path: 'public/.' # Change this line
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
Paste the following code into nano and change the URL and SERVERIP variables at the top
If your page is bugged out and only showing HTML elements and no CSS or Javascript, you likely put the wrong domain in the script above. If you don't have a custom domain, the default should be https://username.github.io/. Also if GitHub pages doesn't work for you, Cloudflare pages also works using this same GitHub repo with zero change to your code. If all of this has worked up till now, congratulations, you're done! I hope this guide helped get you to that point.
Caden Kraft Newsletter
Join the newsletter to receive the latest updates in your inbox.