Deploying Jekyll blog through CI/CD
I recently migrated from a self-hosted Wordpress instance as a CMS to Jekyll. When initially writing posts for Jekyll, my workflow consisted of using the text editor Atom and the Ruby gem bundle
to build a local instance of my website. This works great for previewing changes but quickly falls short when attempting to peer review the post before publishing.
To solve this, I configured a new internal subdomain to host my blog. I executed bundle exec jekyll build
and then rsync’d the _site
directory to an internal webserver. Once I was ready to publish the blog, I then rsync’d the _site
directory to my public webserver.
This became quite repetitive so I decided to streamline the process. I knew that I needed a CI/CD pipeline to help speed up the process but wasn’t sure which one suited my needs. Follow me on my DevOps journey.
I first tried an application called mvo CI, which is written in go. The build/install process seemed a little excessive to me, coming from the ease of getting a gitea instance up and running.
mvo CI installation steps:
- Install the go toolchain
- Add an un-privileged user to the system
- Clone the git repository
- Build the binary using make
- Create a systemd service file to auto-start the service on boot.
Once installed, I used an ssh port forward to connect to port 4042 on my loopback address to the instance running mvo CI: ssh -L 4042:127.0.0.1:4042 root@192.168.1.10
to initialize the database and setup an admin user.
The process to add a webhook in gitea is fairly straightforward. Under the repository settings:
- Select ‘Webhooks’
- Click ‘Add Webhook’
- Select gitea from the dropdown
- Add a target URL for mvo CI and generate a shared secret.
With the install process complete, I made some code commits into a test repository wrote a shell script in the mvo CI web interface.
After using mvo CI for a few days, I found myself looking for more flexibility than what mvo CI provided. I reviewed the Devops section of the awesome-gitea page and decided to try Drone.
The drone CI/CD pipeline is easy to get running, so long as you have an existing docker environment to host it. Steps to integrate drone into an existing git repository can be found on their website.
Once I had the drone server running, I needed a runner to execute the configuration I would later specify in the .drone.yml
file.
The recommended runner if you are just starting out is docker. I downloaded the docker runner and started it up following the documentation.
One of the shortcomings with the docker runner is that it is stateless. This could also be a benefit in some scenarios. What I discovered during this process is that the bundle install
and bundle exec jekyll build
commands would take a little over a minute to complete each time. While not a deal-breaker, I looked for ways to reduce the build time.
I came across a docker image called ‘drillster/drone-volume-cache’ that allowed me to cache a directory that could be re-used in a later execution of the pipeline. To provide the reader with a better understanding of my workflow, a snippet from my drone configuration is below.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
- name: restore-cache
image: drillster/drone-volume-cache
volumes:
- name: cache
path: /cache
settings:
restore: true
mount:
- ./ruby_cache
- name: build
image: ruby
commands:
- mkdir -p ./ruby_cache
- export BUNDLE_PATH=/drone/src/ruby_cache
- gem install bundler
- bundle install
- bundle exec jekyll build
- name: clean exif data
image: tigerj/exiftool
commands:
- exiftool -overwrite_original -P -r -all= -ext png _site/
- name: rsync-to-dev
image: drillster/drone-rsync
environment:
RSYNC_KEY:
from_secret:
SSH_KEY
settings:
hosts:
[ "192.168.20.6" ]
user:
root
port:
50022
source:
_site/
target:
/var/www/blog/
delete:
true
when:
branch:
- develop
- name: rebuild-cache
image: drillster/drone-volume-cache
volumes:
- name: cache
path: /cache
settings:
rebuild: true
mount:
- ./ruby_cache
volumes:
- name: cache
host:
path: /tmp/cache
I will be triggering a separate build step when merging to the main branch in the future. The idea being to make incremental changes in the develop branch and have those changes reviewed before merging from develop to main.
One of the features I really like is the tight integration in gitea. The build status can be quickly located to the right of the commit hash. With mvo CI, one must check a separate UI to determine the build status.
Update: 2022FEB08
When merging changes from the develop branch into the main branch using a pull request in Gitea, Drone would try and create a build before the pull request was merged. This build would fail to read the SSH key from the build environment. Once the pull request was merged, a second build of the main branch would be triggered in Drone. This build would succeed. A bit of research led me to discover that adding the following yaml to the .drone.yml
file would prevent Drone from creating a build whenever a pull request was opened.
1
2
3
4
trigger:
event:
exclude:
- pull_request
Hopefully this saves someone else hours of debugging and research.
Comments powered by Disqus.