Skip to main content

Automatic deployment with Github Actions

·639 words·3 mins
Jim Rhotert
Author
Jim Rhotert
MLOps Engineer

After my SD card died, I started organizing my blog on Github. Since I use Hugo to build this static site, the content and the source code are in the same repository. Having my own site on Github was a good opportunity to deal with something that has been on my bucket list for a while: Learning CI/CD pipelines.

Github offers a very low-threshold CI/CD solution called Github Actions. Much like docker-compose files, you declare your pipeline and its triggers in a yaml file. Whenever some event happens with respect to your repository that you have declared as a trigger in your Github action, your pipeline will be executed by a “runner”, which is something like a container running the latest Ubuntu.

My CI/CD pipelines includes building the site and then deploying it to my self-hosted server whenever I push to the main branch.

name: Build and Deploy
on:
  push:
    branches:
      - main
      - master

env:
  HUGO_VERSION: 0.119.0

jobs:
  build-and-deploy:
    if: "!contains(github.event.head_commit.message,'[skip-ci]')"
    runs-on: ubuntu-latest
    steps:

Building the site is simple, as it only requires me to checkout my own code (recursing submodules for the theme I use),

- name: Checkout repository
  uses: actions/checkout@v4
  with:
      fetch-depth: 1
      clean: true
      submodules: recursive

downloading and installing hugo,

- name: Download Hugo
  run: "wget https://github.com/gohugoio/hugo/releases/download/v${{ env.HUGO_VERSION }}/hugo_${{ env.HUGO_VERSION }}_linux-amd64.deb -O hugo_${{ env.HUGO_VERSION }}_Linux-64bit.deb"

- name: Install Hugo
  run: sudo dpkg -i hugo*.deb

and finally running the binary to build the website.

- name: Build site with Hugo
  run: hugo --minify

Deploying the results found in the public directory of the container is a bit inconvenient, as I need to transfer its contents to my personal server. I chose rsync for this task because it uses ssh, which comes with built-in public key authentication.

First, I create a public/private key pair using elliptic curves with ssh-keygen. These keys are used to establish a secure connection between Github and my server.

user@myserver$ ssh-keygen -t ed25519 -C "hey@example.com"

We need to add the public key to my private server and the private key to Github’s pipeline runner.

We add the public key to my server by placing the it in the ~/.ssh/ directory of the user we want to use for deployment and adding it to the authorized_keys file.

user@myserver$ cat id_ed25519.pub >> authorized_keys

The Github action pipeline needs the private key for the corresponding public key to successfully authenticate itself to my server. For such use cases, Github provides action secrets. Secrets belong to a repository and can be referenced in Github action declarations, without being part of the repository itself. They are not visible to the public and are not copied when the repository is cloned or forked. Over the past few years, I have come across multiple AWS or Azure keys in machine learning research repositories and this feature conveniently solves that problem.

The following things need to be stored as Github secrets:

  • My server address: DEPLOY_SSH_HOST
  • The private key: DEPLOY_SSH_KEY
  • The user who is deploying: DEPLOY_SSH_USER
  • The desired path: DEPLOY_PATH

We can now install the private key and add my server as a known host.

- name: Install SSH Key
  run: |
    mkdir -p ~/.ssh/
    echo "${{ secrets.DEPLOY_SSH_KEY }}" > ~/.ssh/private.key
    sudo chmod 600 ~/.ssh/private.key    

- name: Adding Known Hosts
  run: ssh-keyscan -H ${{ secrets.DEPLOY_SSH_HOST }} >> ~/.ssh/known_hosts

Finally, we call rsync, input the private key, and deploy our website to the desired path.

- name: Deploy with rsync
  run: rsync -avz  -e "ssh -i ~/.ssh/private.key" ./public/ ${{ secrets.DEPLOY_SSH_USER }}@${{ secrets.DEPLOY_SSH_HOST }}:${{ secrets.DEPLOY_PATH }}""

The complete deploy.yaml file that defines the Github workflow can be found here.

I am still a bit hesitant about uploading an ssh key that can log into my server to a foreign entity, even though it should be encrypted and stored securely.

For now though, I am enjoying the magical feeling of deploying directly with git push.

Deployment