Statically generated websites are a growing trend. Traditionally, many websites rely on resource hogging CMSs. These require a running server instance, a database such as MariaDB, and a back of the mind fear that certain file permissions might be wrong leading to an eventual hack reminisces the good ol’ WordPress days. Static website generators such as Hugo and Jekyll are solving this problem by building your site’s files upfront, leaving you with a bunch of HTML, CSS, and JavaScript files you can cheaply serve from anywhere, even GitHub! Hugo and related tools/frameworks even have a trendy concept name now, the JAMStack. Here’s how you can use Hugo and CircleCI to build your own statically generated website on CircleCI 2.0.

Why Hugo over Jekyll?

Anyone who’s heard of statically generated websites might be saying, “this is great but why aren’t you using Jekyll?” Jekyll is also a static site generator and a great one at that. We actually use it to build this very site you’re reading as well as our Docs site (contributions welcome). Why some people, including myself, like to use Hugo is that it’s built with Go (Golang). This gives it three benefits over Jekyll; It’s fast, it’s a single binary so no dependencies, and seriously, it’s fast. See for yourself.

The rest of this post assumes that you are familiar with Hugo and already have it installed locally. If not, I suggest first taking a look at their docs site and more specifically, the install guide.

Building A Hugo Website on CircleCI

Now for what you came here for.

Building a Hugo website on CircleCI 2.0 is fast and simple, so let’s start here. We’ll walk through snippets of an example .circleci/config.yml with the whole file at the end of this post.

version: 2
jobs:
  build:
    docker:
      - image: felicianotech/docker-hugo:0.21

This is standard CircleCI 2.0 syntax. We declare the new, version 2 format, specify that we are using the docker executor, and tell it which Docker image we want to use. The image used here is felicianotech/docker-hugo made by yours truly. It includes Hugo, HTML Proofer for testing, and basic utils needed on CircleCI such as git and SSH. Other images include this and this but they don’t include anything to test your site with.

    working_directory: ~/project
    steps:
      - checkout
      - run:
          name: "Run Hugo"
          command: HUGO_ENV=production hugo -v -s ~/project/src/

Here we set the working directory to ~/project, which is where our website’s repository will eventually be checked out (git clone’d) to and where the following commands will be ran from. After the code is checked out, we run Hugo to build the website. A couple of notes here:

  • This HUGO_ENV=production is how we can set environment variables for Hugo to do special things like only include Google Analytics code in production and not, for example, in a local build. You can safely remove that if you don’t need it.
  • Notice how we use hugo -v instead of hugo -v serve. The latter is useful locally to have Hugo run a test server so we can browse the site. The former is useful in situations like this where we want to build the static files (into ~/project/src/public/) instead of running a server.
  • -s ~/project/src/ This tells Hugo that my Hugo files aren’t in the root of my repository but instead in the /src/ directory. For example, the Hugo config file here would be ~/project/src/config.toml. Adjust based on your repo.
      - run:
          name: "Test Website"
          command: htmlproofer ~/project/src/public --allow-hash-href --check-html --empty-alt-ignore

Now that our site is built and the files located in ~/project/src/public/, we run some basic tests on it. HTML Proofer will make sure that all of the HTML tags make sense, aren’t missing, and checks for broken links. If it catches anything, the build will fail, and CircleCI will let you know via a browser notification and on GitHub/Bitbucket. This prevents publishing new content on your site that is broken.

      - add_ssh_keys
      - deploy:
          command: |
            if [ "${CIRCLE_BRANCH}" == "master" ]; then
              echo 'example.com ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBJiGRY6N9WYQ0vy6cTiwAgNbc6ueJmVo/EafBtmT7bcD6cQMbipYM/KfYQ2lCn2TxqWepZKYoyoVQXgArycCOns=' >> ~/.ssh/known_hosts
              rsync -va --delete src/public/ staticweb@example.com:/var/www/
            fi

This section of configuration is what deploys our website, which has been built and tested, to “production”. First, SSH keys for your server is injected into the build. You can provide one or more private SSH keys to CircleCI via your project’s settings page on the CircleCI website. I suggest generating a new pair of SSH keys specifically for CI use. It’s a good security practice to not reuse keys.

We then have some Bash commands to say, if this is a master branch build, use rsync to upload our files to the production server. The echo command you see is needed here to tell our build what the SSH key fingerprint is for our production server. This way, when rsync connects over SSH, it doesn’t complain about not being able to verify the remote server.

And that covers the complete build process.

More Information

Hugo Versions

If you are using the Docker image from this example, the image tag is the Hugo version. For example, if you wanted to specifically use Hugo v0.20.5, you would use:

    docker:
      - image: felicianotech/docker-hugo:0.20.5

Regardless of when upstream Hugo updates, your CircleCI builds will use this version. You can decide to always use the latest version of Hugo (not recommended for most CI builds due to lack of deterministic builds) by doing the following:

    docker:
      - image: felicianotech/docker-hugo:latest

Further Testing

We test our website in this post using HTML Proofer. Which HTML tags it checks and how can be adjusted with command-line flags. You can read their readme for more information on which flags are available. There are many other types of tests you can run on a website to make sure you’re putting out the highest quality content possible. You can use Selenium to run browser tests in real command-line browsers and more importantly, you can run accessibility tests with a tool like Pa11y. This allows all Internet users to be able to browse your website regardless of any physical or technology limitations.

Other Deployment Examples

The example in this post shows how to deploy your Hugo generated website with rsync. This is just one of many ways deployment can be done. CircleCI provides you with a Linux environment which means any tools that run on Linux will run with us. You could deploy your site to AWS S3 using the awscli command-line tool, SSH, ftp, Heroku, or even git push back up and serve your site on GitHub Pages.

Discussion

Comment, ask questions, or provide feedback for this blog post in this CircleCI Discuss thread.

Full CircleCI Configuration Example

version: 2
jobs:
  build:
    docker:
      - image: felicianotech/docker-hugo:0.21
    working_directory: ~/project
    steps:
      - checkout
      - run:
          name: "Run Hugo"
          command: HUGO_ENV=production hugo -v -s ~/project/src/
      - run:
          name: "Test Website"
          command: htmlproofer ~/project/src/public --allow-hash-href --check-html --empty-alt-ignore
      - add_ssh_keys
      - deploy:
          command: |
            if [ "${CIRCLE_BRANCH}" == "master" ]; then
              echo 'example.com ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBJiGRY6N9WYQ0vy6cTiwAgNbc6ueJmVo/EafBtmT7bcD6cQMbipYM/KfYQ2lCn2TxqWepZKYoyoVQXgArycCOns=' >> ~/.ssh/known_hosts
              rsync -va --delete src/public/ staticweb@example.com:/var/www/
            fi