One of the best things about the Ghost blogging platform is the impressive flexibility in development and deployment options. Of course, with that flexibility comes a commitment to doing a lot of things yourself. I recently configured a Codeship Continuous Integration pipeline for deploying changes to a Ghost theme I maintain, and while it was fairly straightforward, I'm documenting the process for posterity. Feel free to skip over all the CI steps if you're only interested in using Git push to update a theme on a remote server.
Even though I'm the only developer working on this project—I know, I won't enjoy all the benefits of CI—I like using Codeship to run the build and test processes for my theme. This way, I get to keep a nice clean local working repo, and avoid any build scripts on my live server.
The full stack for my Ghost theme looks like this:
- Local development Ghost 1.20.3 server
- Ghost theme running Webpack and various JS / CSS processors
- Origin repo in Github
- Free tier Codeship account using node 6.9
- Digital Ocean Ubuntu 16.04 droplet also running Ghost 1.20.3
Broadly speaking, the CI process looks like this:
- Make local changes, commit, push to Github
- Github triggers Codeship build
- Codeship compiles the theme for production, swaps .gitignore files
- Codeship server pushes to repo on production server
- Production server receives updated theme files, restarts to display changes
Push your theme to a remote repo
You can't push a project directly to Codeship. When you create a Codeship project you'll have a choice to integrate to either Gitlab, Bitbucket or Github. I chose to use Github, but they're all roughly equivalent. So, from your local machine, prepare a repo from your working files. In the primary
.gitignore file, you should ignore any production or build files. My
.gitignore looks like this:
# ignore development / production files /node_modules /assets
Next, create a
.gitignore file to be used after Codeship builds your theme. This ignore list should specify working files, and allow production files to be committed. I call my build ignore file
.codeshipignore and include these files:
# allow assets to be pushed from codeship /node_modules /src build.gitignore
Once you have all the appropriate local files, go ahead and push to your remote repo, then create a Codeship project and give it access to your origin repo.
Configure Codeship to build your theme
Codeship will prompt you for some "Setup Commands." This is where you can specify the environment required for your theme's tests. If you don't have any test commands to run, don't worry about these fields. After you save this setup, you'll want to click the "project settings" button, and navigate over to the "Deploy" tab. This is where Codeship runs build processes.
Because I'm pushing to a theme that's on a production server, I used the
master branch as my deployment trigger. If you push to multiple servers, i.e., a staging and a production server, you might want to configure multiple pipelines.
Once you've established a pipeline, it's time to add your build script (pick the 'custom script' option). These are commands you feed the Codeship server to be executed just like you're in a terminal session.
Here's what my build script looks like:
git config --global user.email "xxxxx" git config --global user.name "xxxxxx" git checkout master mv .gitignore build.gitignore mv .codeshipignore .gitignore nvm install 6.9.0 nvm use 6.9.0 npm install npm run build:prod ls -la git add . git commit -m "deploy" git remote add live ssh://firstname.lastname@example.org/home/git/xxxx-ghost/ git push live master -f
It's a fairly straightforward process: ensure you're on the right version of Node, install Node modules, build the theme from source, swap the .gitinore files, add your server's repo, and push. Codeship's environments reset after each build so it's necessary to add the remote every time.
Configuring your live server to receive Codeship pushes
Next, you need to be sure your live server can receive the Codeship repo. Log into your remote server through ssh, and create a bare repo for Codeship to push to, inside the home dir for the
git user (this user should be created already, assuming you have git installed on this machine). You will also need to ensure the Ghost CLI is installed on your server.
cd /home/git mkdir yourblog.git cd yourblog.git git --bare init
Jump into the 'hooks' directory and create a file called
cd /home/git/yourblog.git/hooks touch post-receive
Open the new file with the editor of your choice, and paste the following lines in, adjusting for the path to your ghost install / theme:
#!/bin/sh git --work-tree=/var/www/yourblog/content/themes/xxxx-xxxx --git-dir=/home/git/yourblog.git/ checkout -f cd /var/www/yourblog sudo /usr/lib/node_modules/ghost-cli/bin/ghost restart
This script will direct git to place the repo files in your existing ghost theme directory, and to use the CLI to restart your blog instance.
Next, we need to ensure this script can actually execute. Change the permissions for
chmod +x post-receive
...and it would be nice if Git owned the proper directories, too:
cd /home/git chown -R git:git yourblog.git cd /var/www/yourblog/content/themes chown -R git:git yourtheme
At this point, we need to ensure Codeship can push to the remote server. Under the General Project Settings tab in Codeship, copy the "SSH public key" and paste it into the
git .ssh dir:
cd /home/git/.ssh touch authorized_keys nano authorized_keys [paste the key and close/save]
There's one last task to complete! We need to ensure the
git user can run the Ghost CLI's restart command without being prompted for a password, since the Codeship server is unable to respond interactively. Open up the sudoers file with this command:
On newer flavors of Ubuntu this opens up a special session in nano, ensuring that the syntax of your sudoers is correct before saving, minimizing the chance that you blow up your system permissions. Go ahead and insert these lines at the end of the file, where
ghost_instance is the name of the specific Ghost blog you have hooked the Git repo to:
# Allow git to restart ghost blogs git ALL = NOPASSWD: /usr/lib/node_modules/ghost-cli/bin/ghost restart ghost_instance
The line added translates like this:
git ALL == allow connections from all hosts
NOPASSWD: == don't require the pw for sudo actions for...
/usr/lib/node_modules/ghost-cli/bin/ghost == the absolute path to the Ghost CLI
Source your bash profile to reload the sudoers list:
That ought to do it.