When I upgraded my home server to a beefier system, I decided to upgrade my git hosting at the same time. I’ve used gitolite for the last several years and it has served me well, but since it was a straight migration from my old gitosis setup, it had about a decade worth of old project cruft built up and it was a good time for a fresh start. Additionally, while I had gotten used to one command deployments using capistrano, edelivery, or simple bash scripts, after seeing the extra benefits of a true CI/CD setup (thanks Cameron!), I really wanted my replacement to easily integrate with a CI/CD system to call me out on any failing tests or compiler/dialyzer warnings in case I forget to check before I commit and deploy. Since I enjoy trying out new languages and was learning docker, I also wanted to make sure it was flexible and had decent docker support. Also, since I don’t make any money on my fun projects, a price as close to zero was really prefered, especially if I could host on my own hardware.

In case it isn’t obvious from the title, I chose gitlab. Gitlab has a free, self-hosted option in their Gitlab Community Edition offering. Since I was trying to make the new server pure docker with a minimum of additional VMs, it was also handy that they had a container version all setup and ready to go. This made evaluating it a one command proposition.

Jekyll is a static site generator, so it needs a ruby environment to build your website, but after building you have straight HTML and do not require a full ruby environment on your web server. This means the deployment process should be rather simple, use a ruby environment to build the master branch after a commit, then push the resulting site to the webserver. My web server is a different machine than the gitlab build server, and I’d rather not put my SSH credentials in gitlab, so I will also create a small script to be run daily by cron to pull down the built site and deploy any changes. With the plan in place, time to pull it all together.

For building the site, I used jekyll’s official docker image. This conviently gives you a base ruby system with jekyll pre-installed. All that is required is running the jekyll build command. Some additional configuration is in place to store the generated site contents using gitlab’s artifact system. The artifacts are set to expire after one week to allow some roll back options (although honestly for a static site it probably makes more sense to just fix the error in a forward change rather than rolling back). This is coupled with a daily scheduled build in gitlab to ensure I never have all artifacts expire. The full .gitlab-ci.yml configuration is below.

image: jekyll/jekyll:latest

stages:
  - package

package:
  stage: package
  artifacts:
    expire_in: 1 week
    paths:
        - "_site/*"
  script:
    - jekyll build

With the gitlab configuration in place, the last bit would be to setup my nginx config and the script to copy over the artifacts and update the live site. The nginx config is setup to host the site from a new directory under /var/www. To pull this off, you have to create an API access key in gitlab first. This will give you a token string that you need in order to access the artifacts that we built earlier. Without this token, gitlab returns a 404 instead of the 403 I would have expected, leading one on a wild goose chase to track down the proper URL (my repo is private, this might be different for a public repo). To pull the latest zipped file down from gitlab, you’ll end up with a command like this, curl --header "PRIVATE-TOKEN: <your_token_here>" https://gitlab.example.com/api/v4/projects/22/jobs/artifacts/master/download?job=package > /tmp/site.zip

A quick anatomy of the curl command, --header "PRIVATE-TOKEN: <token>", should match the API token that you generated earlier in gitlab. To get your project id, 22 in this example, check your main project page and it will be listed just beneath the project name. After than, master, specifies which branch the artifact is for, and the job name, package, should match the name you configured in your .gitlab-ci.yml file. The last bit, > /tmp/site.zip, is for redirecting the output of curl to a file, in this case /tmp/site.zip. It’s worth mentioning that in a serious production setting, it would make more sense to use a filename from the mktemp command, but since this is my personal machine I’m taking some shortcuts. With the file downloaded, it’s as simple as unzipping, syncing directories, and then cleaning up. Add this to cron and you’re done! For reference, my complete script is below, as mentioned earlier, mktemp for your temp file and directory would make more sense if you have anything riding on your deployment.

rm -r /tmp/jason_aderholdt_net
curl --header "PRIVATE-TOKEN: <token>" https://gitlab.aderholdt.net/api/v4/projects/22/jobs/artifacts/master/download?job=package > /tmp/site.zip && \
unzip /tmp/site.zip -d /tmp/jason_aderholdt_net && \
rsync -a /tmp/jason_aderholdt_net/_site/* /var/www/jason_aderholdt_net/

If this post shows up, then we’ll know it all worked! Hopefully we’ll all see this soon!