Hosting a Hugo blog on GitHub Pages with Travis CI

Claudio Jolowicz
May 24 · 7 min read

Read the original article on my blog

Portrait photograph of Victor Hugo published in the widely distributed serial publication entitled Galerie contemporaine, littéraire, artistique. By Étienne Carjat — Bibliothèque nationale de France, Public Domain,

This post describes how to set up a blog using Hugo, an open-source static site generator. The blog is hosted on GitHub Pages, a web hosting service offered by GitHub. The Travis CI continuous integration service is used to deploy changes to the blog.

This post is based on Artem Sidorenko’s article Hugo on GitHub Pages with Travis CI.



Running a Hugo blog on GitHub Pages requires you to set up two GitHub repositories:

  • The first repository is named and holds the Hugo sources.
  • The second repository is named and holds the generated content.

(Throughout this post, replace username with your GitHub username.)

You also need to set up Travis CI such that, when you push a change to , it invokes Hugo to rebuild the site, and pushes the generated content to . GitHub Pages will then deploy the site to

Installing Hugo

Installing Hugo on macOS is easily achieved using Homebrew:

brew install hugo

See Install Hugo for alternatives.

Setting up the blog repository

In this section you set up the repository on GitHub.

Creating the blog

The first step is to generate the files for the new Hugo site:

hugo new site blog

Creating the repository

Initialize a git repository in the newly created directory, and create the initial commit:

cd bloggit init
git add .
git commit -m "Initial commit"

Installing a theme

The next step is to install a theme. For now, stick with the ananke theme recommended in Hugo’s Quick Starttutorial.

git submodule add \ \
git add .
git commit -m "Add submodule themes/ananke"

This command adds the ananke git repository to the subfolder. Using a git submodule has the advantage of allowing you to track upstream changes to the theme.

Configuring the site

Hugo is configured using a file called , which has already been generated for us. Edit this file to set the site URL and the blog title, and to declare the theme you just installed. This is what the file should look like:

baseURL = ""
languageCode = "en-us"
title = "My Blog"
theme = "ananke"

Commit your changes:

git add config.toml
git commit -m "Configure site"

Publishing the repository

You are now ready to publish the repository to GitHub. One convenient way to do so is using hub, a command-line tool for managing GitHub repositories:

brew install hub
hub create
git push origin master

Setting up the repository

In this section you set up another repository, named , for the static content generated by Hugo. GitHub Pages deploys the repository automatically to the site located at

Creating the repository

Let’s start by creating the repository locally:

echo "#" >
git init
git add .
git commit -m "Initial commit"

Note that you created the repository with an initial commit. An empty repository cannot be added as a git submodule, which is what you are about to do in a second.

Publishing the repository

Publish the repository to GitHub using the command-line tool:

hub create
git push origin master

If you browse to at this point, you will see that the site is already live, using the contents of . This is going to be replaced by Hugo-generated content as you finish this walkthrough.

Cleaning up

You can now safely remove your local clone of . You won’t need it anymore.

cd ..
rm -rf

Linking the repositories

You are almost done with the repository setup. The final step is to link the repository to the repository, by making the former a git submodule of the latter.

Return to the repository, and invoke the following commands in its top-level directory:

cd blog
git submodule add \ \
git commit -am "Add submodule public"

The directory is where Hugo generates the content. Adding the repository as a submodule at this exact location makes it easy to push the generated content to .

Continuous Deployment

We can now start to think about Continuous Deployment. Deploying a change such as a new post to the live blog requires several steps:

  1. You push the change to the repository.
  2. Hugo is triggered to rebuild the site content.
  3. The content is pushed to the repository.
  4. The repository is deployed to GitHub Pages.

In this section you set up continuous integration on the repository to achieve steps 2 and 3, using Travis CI. The last step—deploying from to GitHub Pages—does not require further setup.

Setting up a bot account

Travis CI needs write access to the repository to be able to push the generated content to it. Instead of granting the CI job access to your personal GitHub account, and thus to all of your repositories, you will set up a separate bot account with collaborator access to the repository.

Create a GitHub account named , replacing by your GitHub username. This can be done using GitHub’s SignUp page, after logging out of your personal account. The bot account is just a normal GitHub user account.

Note that you need to use a separate email address for the bot account, since GitHub accounts must have unique email addresses. A useful technique in this scenario is subaddressing (also known as plus addressing): Append to the local part of your email address (the part before the sign), and mails to that address will be delivered to your normal inbox.

When you’re done setting up the GitHub account, go to the Settings page for the repository, and add the bot account as a collaborator.

Adding GitHub credentials to Travis CI

With the bot account set up, you can add the credentials to Travis CI.

On Travis CI, go to the Settings page of the repository.

Add an environment variable named .

Set the value to , using the credentials of the newly created bot account.

Ensure that the Display value in build log switch remains in the off position.

Configuring Travis CI

Travis CI is configured by adding a YAML configuration file named to the top-level directory of the repository.

Continuous integration for the repository needs to perform three tasks:

  1. Install Hugo into the CI environment.
  2. Invoke the Hugo command-line tool to rebuild the site.
  3. Deploy the new content to .

The third step is delegated to a shell script, which is the subject of the next section.

Create the file with the following contents:

- curl -LO
- sudo dpkg -i hugo_0.55.4_Linux-64bit.deb
- hugo
- provider: script
script: ./
skip_cleanup: true
branch: master

Note that is required so that Travis does not remove the generated files before running the deployment script.

Adding the deployment script

Create the script in the repository, again replacing with your GitHub username:

#!/bin/bashecho -e "\033[0;32mDeploying updates to GitHub...\033[0m"cd publicif [ -n "$GITHUB_AUTH_SECRET" ]
touch ~/.git-credentials
chmod 0600 ~/.git-credentials
echo $GITHUB_AUTH_SECRET > ~/.git-credentials
git config credential.helper store
git config ""
git config "username-blog-bot"
git add .
git commit -m "Rebuild site"
git push --force origin HEAD:master

You can also invoke this script manually on your machine, after running to rebuild the site. Outside of CI, the script uses your normal GitHub credentials to commit and push the generated content.


Finally, commit the added files and push them to the repository.

git add .travis.yml
git commit -am "CI: Build and push to"
git push

You can now visit to see your blog building. When CI has completed, your blog should be live at

Some remarks about the CI setup

Two remarks about the CI setup.

First, note that CI never updates the repository to point to the new commit in the submodule. It cannot, because the bot account does not have write access to this repository. This means that the repository is left pointing at the initial commit of the submodule.

This is not really an issue, because our site is deployed directly from , rather than from the repository’s submodule.

Second, note that the deployment script force-pushes the generated content, effectively replacing and effacing history. This is no big deal, as the repository only contains generated content.

The reason for force-pushing is somewhat subtle: As mentioned above, the submodule still points to the initial commit in the repository. To perform a normal push you would therefore first need to pull from origin.

Unfortunately, this is impossible because the submodule is checked out in detached mode and has no information about local and upstream branches. Incidentally, this is also the reason why the last argument to the push command is rather than .

Writing a post

Blog posts are written using Markdown syntax, with a YAML preamble called front matter.

Invoke the Hugo command-line tool to generate the source file for the new post:

hugo new posts/

The generated file is located at and looks something like this:

title: "My First Post"
date: 2019-04-27T10:48:29+02:00
draft: true

Use your favorite editor to write the actual post, and view your changes locally using Hugo’s built-in server:

hugo server --watch --buildDrafts

Remove the line from the front matter when the post is ready to be published.

Commit and push. Your new post should go live once CI has completed.


The Startup

Medium's largest active publication, followed by +526K people. Follow to join our community.

Claudio Jolowicz

Written by

Berlin-based software author and musician.

The Startup

Medium's largest active publication, followed by +526K people. Follow to join our community.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade