Deployment with GitHub Actions
The idea
To make my life easier and automate the process of updating this very website you are currently on, I have decided to learn about GitHub Actions. The base idea behind the process was to have an automated build job that runs the Jekyll build script. It would then be followed by a manual deploy job, which will send the built files to my hosting provider via SFTP.
The journey
First I had to learn a bit about GitHub Actions. As it turns out, the general approach is pretty simple. All you
need is a .github/workflows
directory at the root of your project. In this directory, you can set up YAML files that
describe the various actions to be executed in your repository.
In my case, I wanted a build job that takes the current state of the main
branch and builds the _site
directory
from it. Initially I was considering having the job push the built directory into a separate branch. After playing
around with the idea for a while, struggling to get the script going, then realizing I had added the _site
directory
to my .gitignore file, so the script couldn’t even detect any changes to push. Eventually ended up facing a
permissions
hurdle and decided to drop the idea. The script was now to generate an artifact, which I would manually download and
then manually upload into the file server of the hosting provider.
However, since you’ve read the title of this article, you already know I wasn’t going to stop there. Once I had the artifact in my hands, and had to manually upload and unpack it onto the file server, it immediately dawned on me how tedious this task would become. Naturally, the next step was to have another GitHub Action, which somehow teleports the files from the repository to the file server.
For security reasons the ftp:// protocol isn’t exposed on the target host (good!). According to the documentation, there’s two approaches to setting up an FTP connection - FTPes or SFTP. After several attempts at getting an SFTP workflow to trigger correctly, the barrage of errors I was faced with made me look in another direction. Eventually I landed on using rsync over ssh whenever the main branch gets updated. A few updates cycles later a manual activation of the deployment has proven itself more handy, especially in the rapid early phases of expanding the website and recklessly pushing unstable commits to the main branch.
The result
Below is what the workflow file currently looks like. It is very likely going to change in the future, depending on how the website evolves but for now I am pretty happy with it.
name: Build and Deploy Jekyll Site
on:
workflow_dispatch:
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: '3.3.5'
- name: Install Ruby dependencies
run: |
gem install bundler
bundle install
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '22'
- name: Install Node.js dependencies
run: npm ci
- name: Build Tailwind CSS
run: npx tailwindcss -i ./assets/css/main.css -o ./dist/css/style.css
- name: Build Jekyll site
run: bundle exec jekyll build
- name: Upload _site artifact
uses: actions/upload-artifact@v4
with:
name: jekyll-site
path: _site
deploy:
name: deploy
needs: build
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Download built site artifact
uses: actions/download-artifact@v4
with:
name: jekyll-site
path: _site
- name: rsync deployments
uses: burnett01/rsync-deployments@7.0.2
with:
switches: -avzr --delete
path: "_site/"
remote_path: "~/public_html/"
remote_host: $
remote_port: $
remote_user: $
remote_key: $