Automate Hugo deployment with GitLab CI/CD

To make it easier to write new posts I wanted to automate the deployment of updates to my Hugo website.

The goal is whenever a new commit is added to the master branch, the site is rebuilt using Hugo and deployed to my server using rsync.

Quick note: If your site is entirely static, Netlify (I’m in no way affiliated with them) will do this for you along with lots of extra features like generating previews for pull requests. I use it to generate the sceditor.com website where it works well.

Prerequisites

You will need to set up a user and/or key for rsync to connect to your server with.

The Hugo website has instructions for setting up an SSH key if you don’t already have one set up.

GitLab setup

Private key

The private key needs to be added as a variable to the repository so that GitLab CI can access it. From the repository go to:

Settings -> CI/CD -> Variables

Click the add variable and then add the key giving it the name RSYNC_PRIVATE_KEY:

Add a variable in GitLab

Known hosts

Every time SSH connects, it checks the key from the server against ones it has seen before and warns if it hasn’t seen the key before.

Each time GitLab CI/CD is run it creates a new VM instance, so any previously seen host keys are not saved. It’s possible to just always accept new keys but that could open up MITM attacks. To avoid that, it’s best to add the keys from the server to the known_hosts file.

To set up the known_hosts file, add a new variable called RSYNC_KNOWN_HOSTS with the contents of the following command:

ssh-keyscan -H your.hostname.com

CI/CD setup

Finally, add a file called .gitlab-ci.yml with the following contents:

# Use GitLabs built-in Hugo image
# Might be wise to change from latest to a specific version of Hugo
# to ensure nothing breaks when new versions of Hugo are released.
#
# For example:
# image: registry.gitlab.com/pages/hugo:0.81.0  
image: registry.gitlab.com/pages/hugo:latest

variables:
  GIT_SUBMODULE_STRATEGY: recursive

deploy:
  # Set sane timeout, for me it takes ~25 seconds to run so
  # 5 minutes is reasonable
  timeout: 5 minutes
  script:
    # Need SSH client for rsync to use
    - apk add rsync openssh-client
    # Add the key and known hosts
    - mkdir -p ~/.ssh
    - echo "$RSYNC_KNOWN_HOSTS" >> ~/.ssh/known_hosts
    - echo "$RSYNC_PRIVATE_KEY" > ~/.ssh/key
    - chmod -R 700 ~/.ssh
    - eval "$(ssh-agent -s)"
    - ssh-add ~/.ssh/key
    # Build the website
    - hugo
    # Sync the built site
    # IMPORTANT: Make sure to modify the rsync command below
    # for your sever. Remove --dry-run when ready to deploy
    - rsync -crtvz --dry-run --delete ./public/ user@host:/path/to/public_html/
  only: # Only run on main branch
    variables:
      - $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH

Change the rsync command parameters to match your server. I’ve added the --dry-run option set so should be safe to run but it would be wise to backup your server before committing it to the repository.

Check the output and once happy, remove the --dry-run option.

Done!

That’s it, now whenever a new commit is made to the master branch it will be automatically built and deployed.

GitLab offers free users 400 minutes per month to use which should be plenty for most people (takes around ~25 seconds to run for me). If you’re going to be doing a lot of deploys per day, or if you have a lot of websites, GitHub offers 2000 minutes per month for free users which might be a better choice.

Comments