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.
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:
git
repository to the production servergit
remote in our site repositoryLet’s get started.
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:
- ls ~/.ssh/id_rsa
If you get back a line that looks like this, you do not have an SSH key pair configured yet:
Outputls: cannot access /home/demouser/.ssh/id_rsa: No such file or directory
You can create the missing key pair by typing:
- 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:
Output/home/demouser/.ssh/id_rsa
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:
- 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:
- 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:
Outputprodserver
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:
- 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.
- scp -r /tmp/my-website.git username@production_domain_or_IP:
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:
- 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:
- 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.
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:
git
, nginx
, and pygments
nginx
to serve files from a location in our home directorypost-receive
script to deploy new content pushed to our repositoryThe 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
:
- sudo apt-get update
- sudo apt-get install git nginx python-pip
We can then use pip
to install pygments
:
- 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:
- cd ~/my-website
- 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:
Output103902f5d448cf57425bd6830e544128d9522c51 HEAD
103902f5d448cf57425bd6830e544128d9522c51 refs/heads/master
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:
- 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:
uname -i
command produced x86_64
, right-click and copy the link ending in amd64.deb
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:
- cd ~
- 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:
- sudo dpkg -i hugo*.deb
Test that the installation was successful by asking Hugo to display its version number:
- hugo version
You should see a version line displayed, indicating that Hugo is now available:
OutputHugo 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:
- git clone --recursive https://github.com/spf13/hugoThemes ~/themes
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:
- mkdir ~/public_html
Next, let’s open up the default Nginx server block configuration file to make the necessary adjustment:
- 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:
- sudo service nginx restart
Our web server is now ready to serve the files that we put into the public_html
directory.
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:
- 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:
- 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:
#!/bin/bash
GIT_REPO=$HOME/my-website.git
WORKING_DIRECTORY=$HOME/my-website-working
PUBLIC_WWW=$HOME/public_html
BACKUP_WWW=$HOME/backup_html
MY_DOMAIN=server_domain_or_IP
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.
#!/bin/bash
GIT_REPO=$HOME/my-website.git
WORKING_DIRECTORY=$HOME/my-website-working
PUBLIC_WWW=$HOME/public_html
BACKUP_WWW=$HOME/backup_html
MY_DOMAIN=server_domain_or_IP
set -e
rm -rf $WORKING_DIRECTORY
rsync -aqz $PUBLIC_WWW/ $BACKUP_WWW
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:
#!/bin/bash
GIT_REPO=$HOME/my-website.git
WORKING_DIRECTORY=$HOME/my-website-working
PUBLIC_WWW=$HOME/public_html
BACKUP_WWW=$HOME/backup_html
MY_DOMAIN=server_domain_or_IP
set -e
rm -rf $WORKING_DIRECTORY
rsync -aqz $PUBLIC_WWW/ $BACKUP_WWW
trap "echo 'A problem occurred. Reverting to backup.'; rsync -aqz --del $BACKUP_WWW/ $PUBLIC_WWW; rm -rf $WORKING_DIRECTORY" EXIT
git clone $GIT_REPO $WORKING_DIRECTORY
rm -rf $PUBLIC_WWW/*
/usr/bin/hugo -s $WORKING_DIRECTORY -d $PUBLIC_WWW -b "http://${MY_DOMAIN}"
rm -rf $WORKING_DIRECTORY
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:
- chmod +x post-receive
Our deployment system is now complete. Let’s test it out.
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:
- bash ~/my-website.git/hooks/post-receive
This should run your script and output the normal git
and Hugo messages to the screen:
OutputCloning 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:
http://production_domain_or_IP
Now, we can go back to the machine we are using for Hugo development. On that machine, let’s create a new post:
- 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:
- git add .
- 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:
- git push prod master
Now, if you re-visit your production site in a web browser, you should see the new content:
http://production_domain_or_IP
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.
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: