Aaron Lauterer

Static Site Generator - Deployment strategies

Even though there are countless guides for this, I still need a place to document my way of deploying my websites. If it is useful for other people, even better.

Most of my current websites are created with static site generators. In particular with Hugo and Zola. But having to build them locally and then transfer the files to the web server is a tedious and manual task that can be improved upon with the use of git branches and git-hooks.

The basic idea is to use hooks in a git repository to initiate the building of the website with the installed static site generator. It is necessary to have SSH public key auth set up between all involved parties.

Additionally I have a user on the webserver for each website to be able to limit their permissions to the necessary.

Variant 1: Via central repository

The idea is to have a central git server (potentially self hosted) and a clone of the repository on the webser. When new commits are pushed to the publish branch on the central server, the post-receive hook will execute a small set of commands. These include pulling the latest commits from the central repository to the working one on the webserver and building the website.

  1.git push to `publish`      2.hook ssh
+--------+     +----------------+     +-------------+
| client +-----> central server +-----> webserver   |
+--------+     +--------^-------+     +-------------+
                        |             |             |
                        +-------------> working dir |
                         3.git pull   |           + |
                                      |   4.build | |
                                      |           v |
                                      | website dir |
                                      |             |
                                      +-------------+

The hooks/post-receive hook on the central servers repository has the following contents:

echo "Running Hook:"
echo "-------------"

while read oldrev newrev ref
do
	echo $ref;
	case $ref in
		*publish*) echo "DEPLOYING:"; ssh webuser@webserver.example\
        'git --git-dir=/home/webuser/aaronlauterer_com/.git\
        --work-tree=/home/webuser/aaronlauterer_com pull &&\
        cd /home/webuser/aaronlauterer_com &&\
        /usr/local/bin/zola build -o /home/webuser/public_html/';
	esac
done

The following happens if the pushed branch matches the *publish* pattern.

  1. An SSH connection with the user for that website is opened to the webserver
  2. git is being told which .git and the working directory to use and to run a pull
  3. The SSH session is changing directory into the working dir of the repository
  4. The SSG is called to build the website to the directory from which the webserver is serving the website.

One could add a git checkout publish to the commands but in my experience it is enough to do it once during the initial setup, after cloning the repository to the webserver.

Variant 2: Directly to webserver

This variant is useful if there is a lot of data to be pushed in the commit and the connection from the central server is not the fastest. For me this is the situation with my travel blog which contains a lot of photos. A few hundred MiB per blog post is not unusual. Instead of pushing to the central server I send the new publish commits directly to the webserver. Later, when at home and in the same network as the central git server, I can push the other branches (mostly master) way faster to the central git server.

To make this work nicely the repository needs to exists two times on the webserver. Once as a bare repository without a working directory, and as a normal one with a working directory which is pulling locally from the bare one.

     1.git push to `publish`
+--------+          +------------------+
| client +----------> webserver        |
+--------+          +------------------+
                    |                  |
                    |  bare git repo   |
                    |   +              |
                    |   |  2.git pull  |
                    |   v              |
                    |  git working dir |
                    |   +              |
                    |   |  3.build     |
                    |   v              |
                    |  website dir     |
                    |                  |
                    +------------------+

To create a bare repository and a local clone from it run the following commands:

$ git clone --bare git@mygitserver.example:/travel_blog.git ~/travel_blog_bare.git
$ git clone ~/travel_blog_bare.git ~/travel_blog

The hooks/post-receive hook in the bare repository looks like this:

echo "Running Hook:"
echo "-------------"

while read oldrev newrev ref
do
	echo $ref;
	case $ref in
		*publish*) echo "DEPLOYING:" && git\
        --git-dir=/home/travel_user/travel_blog/.git\
        --work-tree=/home/travel_user/travel_blog pull &&\
        hugo -s /home/travel_user/travel_blog/ -d /home/travel_user/public_html/ ;;
	esac
done

In the local repository on my computer where I write new blog posts, I have the following remote in the .git/config:

$ git remote add production travel_user@webserver.example:travel_blog_bare.git

To push directly to the production remote run the following command:

$ git push production publish
Got any hints or questions? blog@aaronlauterer.com