Over the last week I have migrated my photography site from a homegrown setup to Jekyll (which I already use for other projects). One of the reasons behind this was to make use of responsive images, something that wasn’t practical to manage by hand but the jekyll-responsive-magick plugin makes easy. Alas, I run into a small snug with Cloudflare.

I use Cloudflare Pages for all my static sites, the free tier allowances are very generous, and the setup is trivial: you configure a worker to watch a GitHub repository, and to run the appropriate build command, and then to deploy the output. The Pages worker runs on some sort of an Ubuntu image, with tooling for all the usual suspects, including ruby, so Jekyll integration just works.

However, the jekyll-responsive-magick plugin uses imagemagick to generate the scaled images and the associated markup, and it turns out there is no imagemagick on the CF Pages Ubuntu image. Now, it is possible to install it via the asdf tool (instructions readily found online), but when I tried that, it ended up compiling imagemagick from source, which adds several minutes to each build. It might very well be possible to find some version that is available pre-compiled, but it would break sooner or later again, and I couldn’t be bothered to look for it either.

The obvious solution is to simply deploy the build products; the nice thing about that is it not only speeds up the deployment by avoiding the build step on the (underpowered) worker, but it eliminates tooling dependencies altogether, so upstream image updates become a non-issue.

Now, one might be tempted to simply commit the build products in with the sources (i.e., in the Jekyll case to commit the _site directory) — if you have ever harboured such thoughts, or worse, done such a thing, I suggest strongly to keep it to yourself, for it amounts to dilettantism of the highest order, which in a shared environment will lead to murderous thoughts among your co-workers, and even on a solo project will make your life a misery and your git commit history a despicable mess bearing witness against you for ever after.

An orphaned branch to the rescue.

In git an orphaned branch is one that doesn’t share history with the rest of the repository. This means it can contain a wholly unrelated content to the rest of the repository, without causing any issues, which can be very useful: in this case, the orphaned branch will hold the build products to be deployed.

I am going to call mine production:


git checkout --orphaned production
git rm -rf .
touch .gitignore
git add .gitignore
git commit -m 'initial commit'

(A git branch is merely a symbolic name for a commit, so the orphaned branch doesn’t actually exist until we commit something into it, hence I added a .gitignore file to get it going).

Now, let’s generate our build, from whatever branch is appropriate:


git checkout main
bundle exec jekyll build

Back on the production branch, we create a suitable directory for the build products, and copy the build products into it. You can call this directory anything you want, but avoid names that exist in your source code, I am calling mine _build; and I am going to use rsync to do the sync, rather than an inefficient cp:


git checkout production
mkdir _build
rsync -r _site/* _build/

At this point, you might want to visually check that the contents of _build look correct, rsync is a great tool, but it’s easy to get trailing slashes wrong, and end up with the _site directory itself inside _build. If everything looks OK we can commit it:


git add _build
git commit -m 'initial build'

At this point we need to configure the Page worker (or wherever) to track the production branch and deploy the _build directory; once that’s in place, we can deploy the site:


git push origin production

Now that it’s all setup, we can put together a little script to make life easier:


#!/bin/bash
if ! git diff-index --quiet HEAD; then
    echo 'uncommitted changes, cannot proceed ...'
    exit 1
fi

# Don't want to run build when the developer server is running
# (this matches my usecase, adjust as required)
if  ps aux | grep -e 'jekyll serve -P 4006' | grep -v 'grep'; then
    echo 'developer server is running, cannot proceed ...'
    exit 2
fi

set -e

hash=$(git rev-parse HEAD)
branch=$(git rev-parse --abbrev-ref HEAD)

bundle exec jekyll build

git checkout production

rsync -r _site/* _build/

git add _build
git commit -m "build $hash"

git checkout $branch

echo "build products commited to branch 'production'"
echo "to deploy run 'git push origin production'"

I have called this sync.sh and added it to the source tree (and to the exclude section of the Jekyll _config.yml file, so it doesn’t end up in the build).