GitLab, where I have my version control for wllm.no, offer continuous integration/continuous deployment (CI/CD) with a free tier available even for private projects. I wanted to set up continuous deployment so I could make and publish smaller changes without a build environment on my local machine. I also wanted to automate uploading the site to my hosting provider.
A small note – this is not using GitLab Pages. This is a standalone Jekyll build process deploying to an external hosting provider, in my case Domeneshop.
GitLab provides documentation
and example configurations for you to get
started. This blogpost
by Xavier Decuyper was also very helpful in setting up deployment using lftp
.
Setting up the pipeline
The build and deploy process can be outlined in a few steps:
- Set up Ruby build environment and install dependencies
- Build the site using Jekyll
- Deploy over SFTP using
lftp
Before starting on the config, save the SFTP user and passphrase as variables under the repository Settings -> CI/CD -> Variables tab. Don’t commit secrets such as passwords in your config file. Reading values stored as variables is trivial and much more secure.
First we want a base image with Ruby ready to go. We also want to cache RubyGems between builds to increase build performance.
image: 'ruby:2.6'
cache:
paths:
- vendor/ruby
The before_scripts
section can be used to set up the build environment and
install dependencies. This step will vary based on your needs, but for me
the section looks like this:
before_script:
- apt-get update -qy
- apt-get install -y lftp
- ruby -v
- gem -v
- gem install bundler --version '~> 2.0.2'
- bundle -v
- bundle install -j $(nproc) --path vendor
What I’m doing above is updating the software sources – the image is Debian
based – to make sure I’m installing the latest version of lftp
which I need
later. I also upgrade bundler
before I use it to install the dependencies
declared in my Gemfile
. I also print the versions of Ruby, gem
, and bundle
for debugging.
Next is building the site and marking the result as a build artifact:
build:
script:
- bundle exec jekyll build
artifacts:
paths:
- _site
The above is done on all branches so I get confirmation when creating a merge request that the site is still properly configured.
Finally, when the merge request completes and the pipeline runs for the master
branch I run the deploy step:
deploy:
type: deploy
environment: production
script:
- lftp -e "set sftp:auto-confirm yes; open sftp://ftp.yourprovider.com; user $SFTP_USER $SFTP_PASSPHRASE; mirror --reverse --verbose --delete _site/ www/; bye"
only:
- master
A quick explanation of the lftp
-command above:
- Auto-accept the SSH host questions
- Open a connection using the sftp protocol
- Read the user and password from the variables configured in GitLab
- Mirror the contents of the local folder
_site/
towww/
on the host. This deletes any files inwww/
that are no longer in_site
. Back upwww/
if you have content there already in case the command deletes something unexpected. - Close the connection
Here is the complete .gitlab-ci.yml
, also available as a
snippet on GitLab.
# Official language image. Look for the different tagged releases at:
# https://hub.docker.com/r/library/ruby/tags/
image: 'ruby:2.6'
# Cache gems in between builds
cache:
paths:
- vendor/ruby
before_script:
# Update sources
- apt-get update -qy
# Install lftp for deploy
- apt-get install -y lftp
# Print out ruby version for debugging
- ruby -v
# Print out rubygem version for debugging
- gem -v
# Upgrade bundle. The default version is 1.17.x.
- gem install bundler --version '~> 2.0.2'
# Print out bundle version for debugging
- bundle -v
# Install dependencies into ./vendor/ruby
- bundle install -j $(nproc) --path vendor
build:
script:
- bundle exec jekyll build
artifacts:
paths:
- _site
deploy:
type: deploy
environment: production
script:
- lftp -e "set sftp:auto-confirm yes; open sftp://ftp.yourprovider.com; user $SFTP_USER $SFTP_PASSPHRASE; mirror --reverse --verbose --delete _site/ www/; bye"
only:
- master
Troubleshooting
When I first set up the build I did not upgrade bundler
. The first time I
tried to build it broke on the bundle install
step with the message
“You must use Bundler 2 or greater with this lockfile”.
This is where I tell you I’m not a Ruby developer 😄
The Jekyll docs instruct users to run gem install bundler jekyll
. This meant
I ended up with version 2.0.2 on my machine, and that version generated
Gemfile.lock
.
I’m not the only one having problems with bundler
versions
when using the official Ruby Docker-image. I don’t know the Ruby ecosystem well
enough to have an opinion on it, but (probably for the best) the decision has
been made not to ship 2.x by default yet in the images.
So it’s up to us users to upgrade ourselves. Add
gem install bundler --version '~> 2.0.2'
to your build to upgrade.
Optionally, if you can’t or don’t want to upgrade your build pipeline you can
downgrade bundler
on the machine you use to generate the lockfile. Remove the
lockfile, uninstall bundler and install it again while providing the version
you need, then regenerate the lockfile.