How To Deploy a Hugo Site to Production with Git Hooks on Ubuntu 14.04

Published on November 12, 2015
How To Deploy a Hugo Site to Production with Git Hooks on Ubuntu 14.04


Hugo is a static site generator that allows you to easily create and publish web content by writing in simple markup languages. Hugo can parse your content according to supplied requirements and apply a theme in order to generate consistent web pages that can easily be hosted on any web server or host.

In a previous guide, we covered how to install Hugo onto an Ubuntu 14.04 system and use it to create content. In this guide, we will show you how to set up a system with git that you can use to automatically deploy your new content to your production web server.


For this guide, we will assume that you have an Ubuntu 14.04 machine up and running as your development machine as outlined in our Hugo installation guide.

We will be setting up a second Ubuntu 14.04 server to serve our actual production website. On this server, make sure that you have created a non-root user with sudo privileges. You can follow our initial server setup guide to create this account.

Preparing Your Development Server

We will start off on our development server (the server set up through the previous Hugo guide). Log into that server using the same non-root account you used last time.

We need to do a few things on this server to get set up for one-step deployment. We need to:

  • Configure SSH Key Access to our production server
  • Transfer the initial git repository to the production server
  • Add the production server as a git remote in our site repository

Let’s get started.

Configure SSH Key Access to the Production Server

The first thing we will do is configure SSH key access between our two servers. This will allow us to deploy without needing to input a password each time. If you would like to be prompted for a password on each deployment, you can skip this step. Some people like to keep the password prompt during the deploy process as a small chance to reconsider before pushing content live.

First, check whether you already have an SSH key pair configured in your account on the development server:

  1. ls ~/.ssh/id_rsa

If you get back a line that looks like this, you do not have an SSH key pair configured yet:

ls: cannot access /home/demouser/.ssh/id_rsa: No such file or directory

You can create the missing key pair by typing:

  1. ssh-keygen

Press ENTER through all of the prompts to create a password-less key.

If, on the other hand, the ls command gave you a line that looks like this, you already have a key in your account:


Once you have a key pair, you can transfer your public key to your production server by typing this. In the command, substitute the non-root account name that you configured on your production server in the prerequisites phase for this guide:

  1. ssh-copy-id username@production_domain_or_IP

If this is your first time using SSH between these two computers, you will be asked to confirm the connection by typing “yes”. Afterwards, you will be prompted for the user password for the production server. Your public key will be transferred to the production server, allowing you to log in without a password next time.

Test this functionality by asking for your production server’s hostname with the ssh command:

  1. ssh username@production_domain_or_IP cat /etc/hostname

You should not get prompted to enter a password this time. You should receive back the hostname for your production server:


Transfer the Initial Git Repo to the Production Server

Next, we need to transfer an initial clone of our Hugo repo to our production server. We will need this in order to set up a post-receive hook on the production server later. To accomplish this, we need to create a “bare” clone of our git repo and copy it to our other server.

A bare repository is a special git repository with no working directory. In conventional git repos, your project files are kept in the main directory and the git versioning data is kept in a hidden directory within called .git. A bare repo has no working directory for your project files, so the files and directories that are typically kept in the hidden .git folder are in the main folder instead. Bare repos are typically used for remote servers because it simplifies the process of pushing content.

We will create a bare repo from our main Hugo repository in the /tmp directory. Bare repos are usually identified by a trailing .git suffix. To create this copy, we will use the git clone command with the --bare option:

  1. git clone --bare ~/my-website /tmp/my-website.git

We can transfer this bare repository to our production server with scp. Make sure to include the trailing “:” at the end of the command so that the repo will be placed in our user’s home directory on the remote system.

  1. scp -r /tmp/my-website.git username@production_domain_or_IP:

Add a Git Remote for the Production Server

Now that we have our bare git repo on our production server, we can add the production server as a tracked remote repository. This will allow us to push new content to our production server easily.

Move back into your Hugo directory:

  1. cd ~/my-website

All we need to do is to decide on a name for the remote. In this guide, we will use prod. We can then specify the connection information and location of our bare repository on the remote system:

  1. git remote add prod username@production_domain_or_IP:my-website.git

You will not be able to test this remote link until after we install git on our production server.

Setting Up the Production Server

Now that our development machine is completely configured, we can move on to setting up our production system.

On our production system, we need to complete the following steps:

  • Install git, nginx, and pygments
  • Install Hugo and the Hugo themes
  • Configure nginx to serve files from a location in our home directory
  • Create a post-receive script to deploy new content pushed to our repository

Install Git, Pygments, and Nginx on the Production Server

The first thing that we should do is install git, pygments, and nginx onto the server. Although our project repository is already on our server, we need the git software to receive pushes and to execute our deployment script. We need pygments to apply server-side syntax highlighting for any code blocks. We will use nginx as the web server that will make our content available to visitors.

Update the local package index and install git and nginx from Ubuntu’s default repositories. We will need to install pip, the Python package manager, to grab pygments:

  1. sudo apt-get update
  2. sudo apt-get install git nginx python-pip

We can then use pip to install pygments:

  1. sudo pip install Pygments

After the download is complete, we can test that we set up the remote repository correctly on our development machine. On your development machine, move into your Hugo project directory and use the git ls-remote command:

  1. cd ~/my-website
  2. git ls-remote prod

If git can establish a connection between the repositories on your development and production machines, it will display a list of refs, like this:

103902f5d448cf57425bd6830e544128d9522c51 HEAD 103902f5d448cf57425bd6830e544128d9522c51 refs/heads/master

Install Hugo on the Production Server

Back on our production server, we need to install Hugo. Instead of building our content on our development server, we will build the static assets on the production server after each git push. To do this, we need Hugo installed.

We can install Hugo using the same method we used for our development machine. Start by checking the architecture of your production server:

  1. uname -i

Next, visit the Hugo releases page. Scroll down to the “Downloads” section for the most recent Hugo version. Right-click the link that corresponds with your architecture:

  • If the uname -i command produced x86_64, right-click and copy the link ending in amd64.deb
  • If the uname -i command produced i686, right-click and copy the link ending in i386.deb

On your production server, move into your home directory and use wget to download the link you copied:

  1. cd ~
  2. wget https://github.com/spf13/hugo/releases/download/v0.14/hugo_0.14_amd64.deb

Once the download is complete, you can install the package by typing:

  1. sudo dpkg -i hugo*.deb

Test that the installation was successful by asking Hugo to display its version number:

  1. hugo version

You should see a version line displayed, indicating that Hugo is now available:

Hugo Static Site Generator v0.14 BuildDate: 2015-05-25T21:29:16-04:00

Before we move on, we need to clone the Hugo themes to our production server:

  1. git clone --recursive https://github.com/spf13/hugoThemes ~/themes

Configure Nginx to Serve Files Produced During Deployment

Next, we need to modify our Nginx configuration a bit.

In order to simplify our deployment, instead of putting our generated content in the var/www/html directory, the content will be placed in a directory called public_html within our user’s home directory.

Let’s go ahead and create the public_html directory right now:

  1. mkdir ~/public_html

Next, let’s open up the default Nginx server block configuration file to make the necessary adjustment:

  1. sudo nano /etc/nginx/sites-available/default

The only options we should need to change are the server_name, which should point to your production server’s domain name or IP address and the root directive, which should point to the public_html directory we just created:

server {
        listen 80 default_server;
        listen [::]:80 default_server ipv6only=on;

        root /home/username/public_html;
        index index.html index.htm;

        # Make site accessible from http://localhost/
        server_name server_domain_or_IP;

. . .

Make sure that you replace “username” in the root directive with your actual username on the production server. Save and close the file when you are finished.

Restart the Nginx server in order to apply your changes:

  1. sudo service nginx restart

Our web server is now ready to serve the files that we put into the public_html directory.

Create a Post-Receive Hook to Deploy the Hugo Site

Now, we are finally ready to create our post-receive deployment hook script. This script will be called whenever you push new content to the production code.

To create this script, we will go into our bare repository on our production server into a directory called hooks. Move into that directory now:

  1. cd ~/my-website.git/hooks

This directory has quite a few sample scripts, but we need a post-receive script for this guide. Create and open a file with this name in the hooks directory:

  1. nano post-receive

At the top of the file, after indicating that this is a bash script, we will start by defining a few variables. We will set GIT_REPO to our bare repository. We will be cloning this to a temporary repository specified by the variable WORKING_DIRECTORY so that Hugo can access the content within to build the actual site. Because the themes directory in our git repo is actually just a symbolic link pointing to a location in the parent directory, we need to make sure that the working directory clone is created in the same location as the Hugo themes we downloaded.

The public web folder will be specified by the PUBLIC_WWW variable and a backup web folder will be kept accessible through the BACKUP_WWW variable. Finally, we will set MY_DOMAIN to our server’s domain name or public IP address:

With that in mind, the beginning of your file should look something like this:



After setting our variables, we can start on the logic of our script. First, we will use bash’s set -e command to specify that the script should exit immediately if it encounters any errors. We will use this to clean up in the event of a problem in a moment.

Afterwards, let’s make sure our environment is set up for our deployment. We want to remove any existing working directory, as we want to clone a fresh copy during the deployment. We also want to backup our web directory so that we can restore if anything goes wrong. We use rsync here because it handles empty directories and directories with content in them more consistently than cp. Make sure that you include the trailing / after $PUBLIC_WWW so that the caommand is parsed correctly.

One last setup procedure we will do is set the trap command to respond in the event that an “exit” signal is received. Since we included the set -e command, an exit signal will occur whenever a command in our deployment fails. In this case, the commands specified by the trap will restore our backup copy to the web directory and remove any instance of the working git directory.



set -e

trap "echo 'A problem occurred.  Reverting to backup.'; rsync -aqz --del $BACKUP_WWW/ $PUBLIC_WWW; rm -rf $WORKING_DIRECTORY" EXIT

Now, we can start our deployment. We will create a regular clone of our bare repo so that Hugo can access the repo contents. We will then remove all content from the public web directory so that only fresh files are available in the public web directory. Afterwards, we will use Hugo to build our site. We will point it to our new clone as the source directory and tell it to place generated content in the public web folder. We will also pass it the variable containing our production server’s domain name or IP address so that it can correctly build links.

After Hugo builds the content, we will remove the working directory. We will then reset the trap command so that our backup copy does not immediately overwrite our new content when the script attempts to exit:



set -e

trap "echo 'A problem occurred.  Reverting to backup.'; rsync -aqz --del $BACKUP_WWW/ $PUBLIC_WWW; rm -rf $WORKING_DIRECTORY" EXIT

rm -rf $PUBLIC_WWW/*
/usr/bin/hugo -s $WORKING_DIRECTORY -d $PUBLIC_WWW -b "http://${MY_DOMAIN}"
trap - EXIT

At this point, our script is finished. Save and close the file when you are finished.

All we have to do now is make the script executable so that git can call it at the appropriate time:

  1. chmod +x post-receive

Our deployment system is now complete. Let’s test it out.

Test the Deployment System

Now that we have our system set up we can go ahead and test it.

Let’s start off by testing our post-receive hook script. This will allow us to populate our ~/public_html directory with an initial copy of our web content. It will also help verify that the main components of the script are working as expected:

  1. bash ~/my-website.git/hooks/post-receive

This should run your script and output the normal git and Hugo messages to the screen:

Cloning into '/home/justin/my-website-working'... done. 0 draft content 0 future content 4 pages created 0 paginator pages created 0 tags created 1 categories created in 26 ms

If you visit your production server’s domain name or IP address in your web browser, you should see the current version of your site:


Hugo initial site state

Now, we can go back to the machine we are using for Hugo development. On that machine, let’s create a new post:

  1. hugo new post/Testing-Deployment.md

In the new post, just put a bit of content so that we can test our system:

categories = ["misc"]
date = "2015-11-11T16:24:33-05:00"
title = "Testing Deployment"


## A Test of the New Deployment System

I hope this works!

Now, add the content to git and commit the changes:

  1. git add .
  2. git commit -m 'Deployment test'

Now, if everything goes according to plan, we can deploy our new changes simply by pushing to our production server:

  1. git push prod master

Now, if you re-visit your production site in a web browser, you should see the new content:


Hugo new content

Our deployment system seems to be running correctly.


In this guide, we set up a separate production server dedicated to serving our web content to visitors. On this server, we installed and configured multiple components so that Hugo could correctly build and serve our content. We then created a deployment script that is triggered anytime that we push new content to the server from our development machine.

The actual mechanisms involved in our deployment system are rather basic. However, they form the basis of an easy-to-maintain system for getting your local content on your web server quickly and painlessly. Because the deployment process is automated, you will not have to interact with your server to make changes beyond a simple git push.

Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.

Learn more about us

About the authors

Still looking for an answer?

Ask a questionSearch for more help

Was this helpful?

This textbox defaults to using Markdown to format your answer.

You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!

hello @jellingwood and thx for this great overview about Hugo. Since a lot has changed for more than 3 years now, is it possible to update this tutorial please ? I think many people here would appreciate it a lot. And so I will :-)

Nice tutorial! Some ideas for enhancement:

  1. Have only one blog server - writing is done on laptop
  2. Add step to verify changes locally before pushing to master

Try DigitalOcean for free

Click below to sign up and get $200 of credit to try our products over 60 days!

Sign up

Join the Tech Talk
Success! Thank you! Please check your email for further details.

Please complete your information!

Featured on Community

Get our biweekly newsletter

Sign up for Infrastructure as a Newsletter.

Hollie's Hub for Good

Working on improving health and education, reducing inequality, and spurring economic growth? We'd like to help.

Become a contributor

Get paid to write technical tutorials and select a tech-focused charity to receive a matching donation.

Welcome to the developer cloud

DigitalOcean makes it simple to launch in the cloud and scale up as you grow — whether you're running one virtual machine or ten thousand.

Learn more
DigitalOcean Cloud Control Panel