Using Git to deploy to your website

This is a brief post on how I deploy my HTML source files from my local workspace to my website using Git. It describes how I’ve set things up so I can push live changes easily. My website is powered by Hugo, a static site generator, but this guide is applicable to most cases.

The quick summary: after compiling locally, I push my local repository to a managed repository (i.e. GitHub), and then I push it again to a remote repository (my website) and use a post-receive hook to update my website with the compiled HTML files.

There are lots of other ways, more powerful, to manage and deploy your website using Git – there is a great guide on hosting on GitHub pages, for example – but this one works for me and might work for you too. :)


The set-up

Our local repository is a Git repository.

On the remote server, there are three workspaces:

We’re going to push into the remote repository, which will push the files to the temporary directory, and then we will push only the compiled HTML files (in Hugo, everything in /public) to the live public directory. Separately, we push everything from our local repository to the managed Git repository.

The remote repository

After SSHing in and moving into the /home, we’ll create a new /repos directory and initialise a bare repository that will mirror the version control files of our local repository. While we’re here, we’ll also create a new /temp directory for our site – this is where we want the source files to get pushed into.

mkdir temp && cd temp

cd ..

mkdir repos && cd repos
mkdir domain.git && cd domain.git
git init --bare

Now we’re going to set up a post-receive hook. This is where the magic happens.

cd hooks
touch post-receive
chmod +x post-receive

Open up post-receive in whichever text editor you like. This file will be executed every time the remote repository receives a push.

These are the instructions we’re going to push to this hook:

# Set bindings for clarity
export WORK_DIR=/home/temp/
export GIT_DIR=/home/repos/domain.git
export LIVE_DIR=/home/www/

echo -e "\033[0;33mDeleting the current temp/public directory...\033[0m"

# If temp/public exists, then let's remove it
if [ -d "$WORK_DIR/public" ]; then rm -rf $WORK_DIR/public; fi

# Now we push the files into the temp directory
git --work-tree=$WORK_DIR --git-dir=$GIT_DIR checkout -f

echo -e "\033[0;33mChecked out to /temp! Now deleting the contents of the live directory...\033[0m"

# And then we send only the contents of temp/public to the live directory
rsync -avht $WORK_DIR/public/ $LIVE_DIR --delete

echo -e "\033[0;33mSynced to live directory.\033[0m"

The local repository

There is only one necessary thing to do here and that is to tell Git to add a remote for the live repository:

git remote add live ssh://

Now, let’s assume that we’re happy with what our website looks like and we’ve committed all our changes (after running hugo) – let’s put it online! When we use git push live master, the whole project will be pushed to the remote repository, and the post-receive hook starts up.

Try pushing it now.

The process

Note: This borrows heavily from the Hugo documentation.

Besides compiling my site and pushing my changes to the live repository, I also want to push my changes to my managed Git repository (with remote name origin). I also have some Sass compiling to do for my theme, so I automate all of these things in a script called

It looks like this:

echo -e "\033[0;32mDeploying updates...\033[0m"

## compile things here

# Remove the public directory
if [ -d public ]; then rm -rf public; fi

# Build the project

# Add the new public directory
git add public

# Commit the changes
msg="rebuilding site `date`"
if [ $# -eq 1 ] then msg="$1" fi
git commit -m "$msg"

echo -e "\033[0;32mDeploying updates to production...\033[0m"
git push live master

echo -e "\033[0;32mDeploying updates to private repo...\033[0m"
git push origin master

Make sure the file is executable (chmod +x and then whenever you want to push changes to your live site and the private repository, simply:

./ "optional commit message"


There are a few things worth mentioning:

There are lots of other Git hooks to play around with. For example, if you want to reject pushes if your code fails a test or linter, you can adjust the pre-receive hook.

The work tree directory has to be writable by the user who is running the hook (git checkout -f) so double-check permissions if you run into any issues.

If your local files are straight up what you want to push – i.e. there is no compilation and resulting /public directory – then you can skip the /temp and /public parts and push directly into the live directory.

My previous attempts used symbolic links (ln -s) and some other reading offered cp as an option to move files too. Both these solutions ignored deleted files, so I had to delete entire folders and remake them. This is an OK solution, to be fair, but I found rsync and liked it a lot better. Depending on how big your site is, you might prefer differently.

That being said, an earlier version of this pipeline used temporary directories to hold dated versions of the project (i.e. backups), and then symlinked the newest directory to the live directory. This is a nice way to do backups, or even beta releases; just remember to purge so it doesn’t get too crowded after every push.