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.
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
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
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,
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
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
- 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
www/on the host. This deletes any files in
www/that are no longer in
_site. Back up
www/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
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
I’m not the only one having problems with
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
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.