Continuous Deployment for Ghost Themes
DesigningOverload is a Ghost blog. Ghost is great as a framework for blogging, and one of the really excellent things about it is the very flexible theming apparatus for making your blog look great. What isn't immediately obvious is how to really seamlessly push changes to that theme directly to the blog. This post is about my journey to get that working.
The ubiquitous theme for ghost is Casper, which is clean, simple, and well documented. A great starting point for building your own theme. The documentation for how to edit it as a theme is excellent on their own README page, so rather than going into that here I'll focus on how to deploy it directly.
For continuous deployment of other projects I'm previously used both CircleCI and Codeship. As I've been using the former most recently for sqlfluff, that is the one that I'll be using for this project.
The last piece of this puzzle is the infrastructure for running the blog itself. For simplicity this blog is running on DigitalOcean, using their ghost 1-click app. If you've never used a web hosting platform before, DigitalOcean is a nice balance between simplicity and functionality. For people who've used full-bore AWS or GCP before, you won't find all the same features in DigitalOcean, but for simple projects there's plenty there for you.
Step by step
-
If you haven't already, find a theme that you like on Github (I chose Casper, but this should work with others too), and fork it. Name the new respository something different and update the README file so that people don't get confused. I named my fork OverloadCasper and you can find it here. Make sure you set up your new repository as a public repository, otherwise some of the services we'll rely on being free later won't be.
-
Within the newly forked repository, find the
package.json
file and update it so that your new theme will look like a new package to anything that tries to load it. The summary of the fields that I've updated are:{ "name": "overload-casper", "description": "The theme for designingoverload.com, forked from Casper", "demo": "https://designingoverload.com", "version": "0.2.0", ... "author": { "name": "Alan Cruickshank", "email": "seegithub@ifyoureally.care", "url": "https://designingoverload.com" }, ... "repository": { "type": "git", "url": "https://github.com/alanmcruickshank/OverloadCasper.git" }, ... }
-
Now we have a theme, we need to get something to build it automatically. This is where CircleCI comes into the picture. They offer free accounts for open-source projects, so head over to their site and set up a free account. Once you're set up and linked to your GitHub account it will ask which repositories you want to follow, select the repository which you created in step
-
Pretty soon CircleCI will ask you to configure the jobs and workflows for your project. You do this with a
yaml
file placed at.circleci/config.yml
in your repository. How to set this up for a ghost theme is very not obvious. A very simple starterconfig.yml
file which should work for your theme would be:version: 2 # Define the jobs available jobs: build: docker: - image: circleci/node:10.18 working_directory: ~/repo steps: # Fetch the code from Git - checkout # Try the standard install for a ghost theme - run: npm install # Test that the theme builds - run: yarn test # Define the workflows to run workflows: version: 2 build_and_deploy: jobs: - build
-
Try making a small change to your repository, and pushing the changes to your remote on Github. You should see a build start in the interface for CircleCI, in the same form that you can see the equivalent builds for OverloadCasper here.
-
Congratulations, you're now automatically testing changes to your theme. The theme still isn't on your blog itself though. To do that we need to integrate our DigitalOcean instance with CircleCI, and we'll be doing that using ssh. Safety warning: Be very careful where you put the ssh keys to your instances. They will grant access to your servers for any attackers who can then alter/delete/copy your data in any way they like. YOU HAVE BEEN WARNED!. You should generate a fresh ssh key just for this purpose, github has quite a good guide on how to create a fresh ssh key on your platform.
-
SSH onto the box you've been using to host your ghost blog. First, using the
root
user, open up the/etc/ssh/sshd_config
file. At the very end you'll want to add a line which saysAllowUsers ghost-mgr
, this enables ssh access for theghost-mgr
user which is used to... manage ghost. Next you'll want to switch to that user (usingsudo -i -u ghost-mgr
) to say what keys are allowed to log in as it. Create a directory in thehome
directory for that user called.ssh
and in there create a file calledauthorized_keys
. In there we want to add a new line at the end, adding the public part of your new key. It will look something like:ssh-rsa AAAAAAAALonGstr1ng0fnumb3r5AnDL3tters= Potentially with a Comment
You should now be able to ssh onto that box using an ssh client of your choice.
-
Head into the CircleCI interface, go to the settings for your project and there should be an option labelled
SSH Permissions
, and within that a button to add a new ssh key. Leave the hostname blank, and paste in the private key (in openssh format, which you'll need to specifically select if you're usingputty
to generate keys on windows). This will then be the default key to use if CircleCI needs to try and ssh onto a box as part of any of the workflows associated with this project. -
Almost everything is now in place. The last things to do are to make sure the theme is packaged properly, send it to your live server and then switch to the new theme. To do the first of those, alter the
.circleci/config.yml
file slightly to add a line to make azip
package of the theme, and then we'll persist that file to the circleci workspace. It will now look something like...version: 2 # Define the jobs available jobs: build: docker: - image: circleci/node:10.18 working_directory: ~/repo steps: # Fetch the code from Git - checkout # Try the standard install for a ghost theme - run: npm install # Test that the theme builds - run: yarn test # Build the package - run: yarn zip # Store in a way we can reach later - persist_to_workspace: root: dist paths: - "*.zip" # Define the workflows to run # <same as before>
-
We'll now set up a second job to take this file that we're persisted and copy it onto our remote server. The full example is in the
config.yml
file for OverloadCasper here. The important things to note are:
- in the
workflows
section near the end, there's a reference to a new job calleddeploy_master
which depends on thebuild
job. - further up the file, that
deploy_master
job is defined with a few steps that first attach the previosly persisted workspace, then recognise the host fingerprint of your remote server in theknown_hosts
file. Finally, we cope the file across, unzip and copy it into the revelant directory, restart ghost and then do some cleanup. - the bash commands refer to
OVERLOAD_HOST
,OVERLOAD_FINGERPRINT
andOVERLOAD_USER
, all of which are environment variables configured within the circleci interface. Putting them here, means you don't have to put them in your public github repo, and means that your data remains more secure.OVERLOAD_HOST
should be the public IP address of your remote server on DigitalOcean.OVERLOAD_USER
should beghost-mgr
as this is the user we configured earlier.OVERLOAD_FINGERPRINT
should be of the formecdsa-sha2-nistp256 AAAXYZ...=
and you can find by runningssh-keyscan localhost
while ssh'd onto your remote server.
And that's it. The first time you do this you'll need to go into the interface for your ghost blog and manually switch to the new theme, but from that point onward, this automation will replace that original theme and so any updates will flow through automatically.
This post was also written retrospectively, so there may a few steps which might need a little extra customisation for your use case. If you find them and think that it's worth updating this little tutorial, feel free to raise them as issues on the GitHub page for OverloadCasper.
Good luck!