Auto-Deploy Hugo from Github
GitHub is pretty good with out-of-box options for auto-deploying Hugo or Jekyll sites to GitHub Pages. But say you have your own Linode (or any other) web server - you can still take advantage of GitHub Actions which is a surprisingly versatile piece of CI/CD automation to rival anything you can do on the big cloud providers.
Pre-requisites
- You have your Hugo site checked in to a GitHub repository
- You have a Linode (or other) web server with SSH access
- You have a good
.gitignore
setup so that the Hugo build files are not checked in to your repository a good Hugo example of .gitignore here
An example .gitignore
that should be checked in to the root of your project:
If you have anything under public
or resources/_gen
checked in to GitHub, you will get errors when GitHub Actions tries to build your site. To clean up your repository of these, run the following from the root of your project:
git rm -r --cached public # recursively remove public from git tracking
git rm -r --cached resources/_gen # recursively remove resources/_gen from git tracking
Using --cached
means it won't touch any of your local files.
Now commit and push all changes.
Step 1: Create an SSH key pair
SSH in to your server and create a new SSH key pair: Leave the passphrase empty when prompted - it is the key that will authenticate.
cd ~/.ssh # make sure it puts the keys in the right place!
ssh-keygen -t ed25519 gh_deploy_key
This will create two files
~/.ssh/gh_deploy_key
(private key)~/.ssh/gh_deploy_key.pub
(public key)
Now add the public key to the authorized_keys file:
cat ~/.ssh/gh_deploy_key.pub >> ~/.ssh/authorized_keys
Now we need to grab the private key and add it to GitHub as a secret.
$ cat ~/.ssh/gh_deploy_key
-----BEGIN OPENSSH PRIVATE KEY-----
...
-----END OPENSSH PRIVATE KEY-----
Copy everything from -----BEGIN OPENSSH PRIVATE KEY-----
to -----END OPENSSH PRIVATE KEY-----
including those lines.
If this next step seems counterintuitive, When deploying FROM GitHub TO your server: It’s similar to how you SSH into any server: So in this deployment scenario: That’s why we: If we did it the other way around (private key on server, public key on GitHub), GitHub Actions wouldn’t be able to authenticate itself to your server - it would be like trying to unlock a door while leaving the key inside the house. But I digress …let me explain …
In your GitHub project, in the web interface that is, go to Settings > Secrets and Variables > Actions
.
Create a new Repository Secret called SSH_DEPLOY_KEY
and paste the private key in to the value field.
yes I know …
Yes I know GitHub has a thing called Deploy Keys, they are for granting access to the repository only. If you were to automate this the other way round (a script on the server pulling from GitHub) then you would use the Deploy Keys section.
Step 2: Create a GitHub Actions workflow
From the root of your repository, create a new directory .github/workflows
and in there create a new file deploy.yml
with the following content:
name: Deploy Hugo site to Linode
on:
# Runs on pushes targeting the default branch
push:
branches: ["master"]
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
concurrency:
group: "linode"
cancel-in-progress: false
# Default to bash
defaults:
run:
shell: bash
jobs:
# Build job
build:
runs-on: ubuntu-latest
env:
SERVER_USER: your_username
SERVER_HOST: your_server_host.com
DEPLOY_PATH: /var/www/your_website/html/
steps:
- name: Install latest Hugo CLI
run: sudo snap install hugo
# you may or may not need this depending on your theme, but harmless if not
- name: Install Dart Sass
run: sudo snap install dart-sass
- name: Checkout
uses: actions/checkout@v4
with:
submodules: recursive # makes sure to get submodules
lfs: true # Fetches LFS data, if applicable / harmless to leave in if not
fetch-depth: 1 # no need to fetch the entire history for deployment
- name: Build with Hugo
env:
HUGO_CACHEDIR: ${{ runner.temp }}/hugo_cache
HUGO_ENVIRONMENT: production
run: |
hugo --minify
- name: Set up SSH
run: |
mkdir -p ~/.ssh
echo "${{ secrets.SSH_DEPLOY_KEY }}" > ~/.ssh/deploy_key
chmod 600 ~/.ssh/deploy_key
ssh-keyscan -H tadg.ie >> ~/.ssh/known_hosts
- name: Deploy to web server
run: |
rsync -avz --delete -e "ssh -i ~/.ssh/deploy_key" ./public/ "$SERVER_USER"@$SERVER_HOST:"$DEPLOY_PATH"
# GitHub kills the connection when we try to clean this up manually, so we
# have to trust their own cleanup process I guess ... hmph
- name: Cleanup
run: rm -rf ~/.ssh/deploy_key
Pay attention to the env:
section and adjust as appropriate for your server
Also this example deploys from the master
branch when triggered. You can adapt branches:
as necessary.
Then add, commit and push to the master (in this case) branch.
In the GitHub web interface, you should see your job already kicking off under Actions.