<- back to Blog

Deployment with GitHub Actions

January 21, 2025

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: $

Written by Nedko Chulev