Deploying Hugo on NearlyFreeSpeech.NET with Git

I’ve been hosting this site with NearlyFreeSpeech.NET for a little over 2 years now, and have been using Hugo to generate it for slightly longer. I decided it might be a good idea to dump my setup somewhere so that I can refer back to it at a later date, and since I’m doing so I may as well make the details public as a post.

Installing Hugo on NearlyFreeSpeech

The first step is to install the Hugo binary on NearlyFreeSpeech. I personally use a simple bash script to do this which allows me to bump the version used when needed and simply re-run it to upgrade:

#!/usr/bin/env bash

set -ex


mkdir -p "${BIN_DIR}"

curl -L --output hugo.tar.gz "${VERSION}/${FILENAME}"
tar -C "${BIN_DIR}" -xvzf hugo.tar.gz hugo
rm hugo.tar.gz

This downloads the FreeBSD x64 version 0.52 of Hugo from the GitHub release, grabs the hugo binary from the archive and drops it into $HOME/bin/hugo and then cleans up the download.

Personally, I don’t add Hugo to my $PATH, as the only time I actually interact with it is via a Git hook - more on that below…

Setting up a Git repository on NearlyFreeSpeech

I deploy the site to the server using Git - I simply add the repository on my NearlyFreeSpeech server as a Git remote (nfsn) and whenever I want to push an update to the site it’s as simple as a git push nfsn master.

Create the repository on the server

First step is to create an empty Git repository on the server. I name this repository, but obviously this should be changed if your site isn’t called that…

cd $HOME
git init --bare

Create a checkout directory for the commit hook to make use of

Next we need to create a directory on the server that will contain a checkout of the repository that Hugo will work on:

cd $HOME
git clone $HOME/

Create the Git post-receive hook

The final step on the server is to add a post-receive hook to the Git repository on the server. This hook will run after a git push occurs, and will checkout the Git repository to the above checkout directory, then run Hugo to publish the updated site.

First, create the hook file and make it executable:

cd $HOME/
touch hooks/post-receive
chmod ug+x hooks/post-receive

Next it’s time to actually write the hook script:


set -ex



git --git-dir=$GIT_DIR pull -f

$HOME/bin/hugo --cleanDestinationDir --minify -d $PUBLIC_WWW


This script first navigates to the site checkout directory ($HOME/, then pulls the latest copy of the site source. Finally, it builds the site using Hugo, outputting the generated content to the public website directory. I also personally enable Hugo’s built in minifier to strip insignificant whitespace from the generated HTML.

Add the new remote to your local repository

In order to be able to push to the server, you need to add a new Git remote to your local repository containing your site:

git remote add nfsn [email protected]:/home/private/

Obviously, user and server depend on your NearlyFreeSpeech username and server name - both of these can be found in the NearlyFreeSpeech.NET Member Interface under the sites tab within the SSH/SFTP Information.

Deploying an update

Now that Hugo is installed and the Git hook is configured, updating the site is as simple as:

git commit -m "..."
git push nfsn master

The Git push proceeds as normal, then runs the post-receive hook which echos out all that it does (thanks to the set -x flag we included):

Enumerating objects: 16, done.
Counting objects: 100% (16/16), done.
Delta compression using up to 8 threads
Compressing objects: 100% (10/10), done.
Writing objects: 100% (10/10), 3.77 KiB | 3.77 MiB/s, done.
Total 10 (delta 5), reused 0 (delta 0)
remote: + SITE_CHECKOUT=/home/private/
remote: + GIT_DIR=/home/private/
remote: + PUBLIC_WWW=/home/public
remote: + cd /home/private/
remote: + git --git-dir=/home/private/ pull -f
remote: From /home/private/
remote:    ef9d151..7afb7dd  master     -> origin/master
remote: Updating ef9d151..7afb7dd
remote: + /home/private/bin/hugo --cleanDestinationDir --minify -d /home/public
remote: Building sites …
remote:                    | EN
remote: +------------------+----+
remote:   Pages            | 37
remote:   Paginator pages  |  0
remote:   Non-page files   |  0
remote:   Static files     |  6
remote:   Processed images |  0
remote:   Aliases          | 11
remote:   Sitemaps         |  1
remote:   Cleaned          |  0
remote: Total in 681 ms
remote: + exit