Ghost Static Pages part 1: Set up local Ghost for static pages generation
4 years ago, I have devised a Docker based pipeline for publishing static web pages from a local deployment of Ghost and publishing them to Gitlab using git.
I used Ghost 1.x, and the static page generator I used was a Python script that was an already abandoned project on Github and designed to work on early versions of Ghost.
I kept a fork ot it, and I had to patch the script and around the script (my Python is not great) to get the whole thing working and do it again from time to time to keep it working.
However, I couldn't update Ghost as the python script would stop working with more recent version and I had one too many failure (the last straw), so I've decided to rebuild from scratch. I will keep Ghost as the centrepiece as the reason I liked it in the first place is still valid today: fantastic Markdown-based graphical user interface for writing posts, tag-based site organisation and very simple infrastructure and operation (no web server to hook - although I kept a basic one for local preview -, no relational database server - It used embedded SQLite3) and a properly marked up web output for published articles. There are evolutions in the blogosphere and in Ghost's business model, and I have discovered the Fediverse and the Gemini protocol, all that made me starting to investigate various options (I'll touch on this in Part 4 hopefully), but for now I am very happy with it.
I will keep the same workflow as in my old pipeline, but this introductory post will focus on the first two transitions for exporting Ghost's posts as static web pages and previewing it.
┌──────────────────────────────┐ ┌──────────────────────────────┐ ┌──────────────────────────────┐ ┌──────────────────────────────┐ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ export │ │ preview │ │ git push │ │ │ Post editing and management ├────────────────────► Static pages on file system ├────────────────────────►Site served on local web server──────────────────►Site served on GitLab Pages │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └──────────────────────────────┘ └──────────────────────────────┘ └──────────────────────────────┘ └──────────────────────────────┘
- Sqlite3 (fast, lightweight, embedded SQL database engine)
- docker (Implementation of Linux containers with productive developer tooling)
- npm (Nodejs Package manager)
- ghost-static-static-generator, a.k.a gssg (a NPM package for generating static pages from a Ghost deployment)
- Nginx (Web server)
- wget (Web client)
- Ghost (A web-based commercial content publishing system for NodeJS with a free open-source self-hosted version)
Prepare the Dockerfile for the static page generator
I don't have to do this step and could use
gssg directly on the computer.
Prepare the docker-compose file:
We will need three container services:
- editor: that's the Ghost web application
- export: that's the
gssgstatic site generator
- preview: set up a web server pointing to the generated site for local previewing
I picked Ghost 4.x over the latest Ghost 5.x because I want to use Sqlite3:
I dont' want to operate a client/server RDBMS (the MySQL option) and I dont' need it since I don't serve the web site from the Ghost webapp directly. Ghost 5.x only support MySQL while Ghost 4.x is the lastest to support the embedded Sqlite3 .
volumes block is not strictly needed to get this workflow working, but it allows me to have the Ghost data and its configuration file outside of Docker so to be easily manageable (backup and further potential customisation).
$REMOTE_URL contains the public url of my blog. Docker expects it to be defined in an
.env file sitting alongside the Docker compose file. The
build: directive indicates that container service is using the default Docker file we have defined earlier. The
command: line is the actual invocation of the static site generator. The
--ignore-absolute-paths parameter tells
gssg to make all links relative to the site root. In preview mode it allows to navigate to all pages locally instead of jumping to remote site when navigating to the aggregations pages (like tags). The
volumes: block is the most important part, this is how we can retrieve the static pages that makes up our site on our computer in the
preview container service allows me to navigate with my web browser to
http://localhost:9999 and see the static version of the web site there.
gssg has an internal previewing mechanism that function on the same principle, but it required installing an additonal NPM package and I wasn't able to get it working within Docker context. The approached I've taken, based on the
nginx web server is copied from my previous blog publishing pipeline (also Docker based) I've used for years and worked fine for my need.
Dealing with bugs in gssg
The premise of
gssg is that it can generate static page from a local deployment of Ghost (reachable at
https://pommetab.com), as well as from remote deployments. By default it expects a local deployment. In order to generate static pages for remote deployment of Ghost, one needs to use a
--domain <url of remote Ghost deployment> parameter.
When using Docker, the hostname for the Ghost deployment in the Docker compose context is the container service name (in our case
editor), and since we run
gssg in the same context, we would need to specify
--domain http://editor:2368 to the command otherwise it won't be able to connect to the webapp running as a container service.
The problem is that
gssg has an issue whereby for some files (Site Maps XML files) it forgets to use the value of
--domain and use
https://pommetab.com instead which is probably hard-coded somewhere in the codebase.
To work around this issue, we add additional configuration directives to our container services so that:
editorcontainer is assigned a fixed IP address within the Docker network
exporthas an extra host mapping from
localhostto that IP address
Then we don't need to use
gssg thinks it is dealing with a local deployment all the way.
This a known issue to the
gssg developers and there is a Github ticket for it. 
With the above fix added, the full
docker-compose.yml looks like this:
$ docker-compose build export $ docker-compose up -d editor preview $ docker-compose run --rm export
By now, I have a project where I can spin up a Ghost editor, write a blog post, then (re)generate the static version of the web site.
Provisional plan for follow-ups:
- Part 2: Publish the generated site to a staticpage-hosting forge (Github, Gitlab)
- Part 3: Deploy the setup on iPad using iSH
- Part 4: Thoughts on the state of blogging and future ideas
This post is my first one using the system described here. Until part 2 is done, I have to manually push the generated site to the last step of my old blogging pipeline.