Coding Babble  Code it once more, with feeling.

How to Set Up CI/CD for a Hugo Project

By Luke Simpson

Introduction

In this post I’ll explain how to set up CI/CD for a Hugo project that uses GitHub for version control and is hosted on a Digital Ocean droplet. I’ll assume you already have a repo and droplet set up on those platforms and that you have an existing site that you can visit.

The plan is that we’ll have GitHub spin up a VM via a GitHub Action, connect to our droplet via SSH, and run the necessary commands to pull down the latest changes and rebuild the site.

We’ll need two sets of SSH keys. One set will be for your droplet to connect to GitHub and pull down a repo. The other will be for GitHub to connect to your droplet and run commands.

Prerequisites

Please follow GitHub’s guide on how to set up SSH authentication and pull down a repo.

Ensure your key is automatically added to the SSH agent when you login to the droplet by adding the identity to your ~/.ssh/config.

Host github.com
    User git
    IdentityFile ~/.ssh/<your key>

SSH for CI/CD

Generating the keys

  1. Login to your droplet.

  2. Navigate to ~/.ssh.

  3. Generate a public and private key pair with:

    ssh-keygen -t ed25519
    

    -t specifies the encryption algorithm to use. It doesn’t matter too much, but I prefer the newer EdDSA (Edwards-curve Digital Signature Algorithm), and GitHub supports it.

  4. When prompted enter a file name:

    Enter file in which to save the key (/home/user/.ssh/id_ed25519):
    

    The files will be called name and name.pub for the private and public keys, respectively.

    One quirk about ssh-keygen here. If you use the default name, your keys will appear in ~/.ssh by default. If you provide your own name, as we just did, they will appear in the current working directory (cwd) instead. That’s why your cwd should be ~/.ssh.

  5. Add <name>.pub to your authorized_keys file:

    cat <name>.pub >> authorized_keys
    

    >> means we want to append to authorized_keys on a new line.

Secret variables

We’ll need to add some secret variables that our GitHub Actions can use.

  1. Show the contents of the private key you just generated in the terminal.

    cat ~/.ssh/<name>
    
  2. Copy the contents to your clipboard. Ensure there are no weird newlines occuring due to a narrow terminal when you copy.

  3. Go to your repo on GitHub.

  4. Click the Settings tab > Secrets and variables > Actions.

  5. Click “New repository secret”. Repeat for the following three secrets:

    • PRIVATE_KEY = content of the private key
    • SERVER_IP = IP of your droplet
    • SERVER_USER = the user you want to login to your droplet with
  6. Now that the private key is in a secret variable on GitHub, you should delete the one that you generated on the droplet. A private key should generally only exist in one place so there is less of a chance it is compromised.

Setting up GitHub Actions

Now we can move on to automating our deployments. We’ll add a simple action to start and build on it.

Add .github/workflows/deploy.yaml to the root of your repo with the following content:

name: Deploy

on:
  push:
    branches:
      - main

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: echo
        run: |
          echo "It's just too easy."          

This creates an action named Deploy that will trigger whenever we push to the main branch. GitHub will spin up a VM with the latest Ubuntu version and echo a message to the terminal. GitHub will also keep a history of these actions.

To see the outcome of the action:

  1. Push the changes to your main branch.
  2. Click the Actions tab of your repo.
  3. Click your Deploy action on the left.
  4. Click your latest commit.
  5. Click the deploy job.
  6. Click the echo step and observe the message.

Let’s update with the necessary code to make the SSH connection.

name: Deploy

on:
  push:
    branches:
      - main

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: create private key
        run: |
          mkdir ~/.ssh
          touch ~/.ssh/id_ed25519
          chmod 600 ~/.ssh/id_ed25519
          echo "${{secrets.PRIVATE_KEY}}" >> ~/.ssh/id_ed25519          
      - name: create known hosts
        run: |
          touch ~/.ssh/known_hosts
          ssh-keyscan ${{secrets.SERVER_IP}} >> ~/.ssh/known_hosts          
      - name: start agent
        run: |
          eval "$(ssh-agent -s)"
          ssh-add ~/.ssh/id_ed25519          
      - name: ssh
        run: |
          ssh -T ${{secrets.SERVER_USER}}@${{secrets.SERVER_IP}}          
      - name: exit
        run: |
          exit          

Key takeaways:

Pulling the repo and building our Hugo site

For this step, I’ll assume you already have a site configured and that you’re serving the public folder that Hugo generates. I’ll also assume that you can pull down GitHub repos to your droplet as soon as you login without adding the key to the SSH agent.

Make these modifications to the ssh step:

- name: ssh
  run: |
    ssh -T ${{secrets.SERVER_USER}}@${{secrets.SERVER_IP}} << EOF
         cd /root/of/your/hugo/project
         git pull
         rm -rf public
         hugo --gc --minify
         EOF    

This is called a heredoc (here document). The first token after << is where we specify a delimiter. The remote machine will execute the commands until it reaches EOF. If your build process will involve something more complex, you could create a script on the droplet and execute the script via SSH.

I like to recreate the public folder each time, but that is optional.

Hugo arguments used:

If you push to your GitHub repo now, it should kick off the action and update your site!

Conclusion

Now you can add new posts or make quick edits to your Hugo site and deploy them with ease.

Support the blog! Buy a t-shirt or a mug!

Tags: