7 minute read Published: Author: Derek Laventure
Hugo , GitLab , DevOps

At Consensus, we spend a fair bit of our time building complex Drupal systems and the infrastructure that supports them. In that context, the simplicity of a tool like Hugo caught my attention. Hugo is a static site generator written in Go that has proven elegant and flexible for many situations where a simple (or even slightly complex) website is called for, and the venerable Drupal is overkill.

Perhaps one of the best uses we’ve found for this tool is to provide technical documentation for a development project. Hugo reduces the website to a handful of plain text files (some Markdown, some YAML, some HTML/CSS), so it’s easy to drop it into a sub-folder of the main project, meaning it lives alongside the code of the site/application itself. This makes it easier to maintain and refer to in the course of regular development workflow.

Here’s a quick rundown of how to get started incorporating a Hugo docs site into your existing development project.


  1. Hugo Setup
  2. Create some content
  3. Serve it up (locally)
  4. GitLab Pages
  5. Conclusion

Hugo Setup

Once you Install Hugo on your computer, creating a new site is easy: initialize a new site, grab a theme, and do some basic configuration. The Learn theme is a reasonable starting point for a docs site.

Assuming you are starting from an existing Git project at $PROJECT_ROOT, do:

hugo new site docs
git submodule add https://github.com/matcornic/hugo-theme-learn.git docs/themes/learn
git add docs
git commit -m "Initialize docs site"
cd docs     # Your docs site lives in here


TOML is the default format, but we prefer YAML. We will replace the default config.toml with a config.yaml. The template below is a good starting point, but note that you’ll need to replace the GROUP, and PROJECT parameters to match your GitLab project. You can also fill in various details like Title, Description, Author, and various feature flags.

Note: If your project is in a GitLab sub-group, the baseURL and editURL parameters below will need to include the slug for the sub-group path. ie. http://GROUP.gitlab.io/SUBGROUP/PROJECT.


baseUrl: "http://GROUP.gitlab.io/PROJECT/"
languageCode: "en-US"
defaultContentLanguage: "en"

title: "My Project Docs Site"
theme: "learn"
metaDataFormat: "yaml"
defaultContentLanguageInSubdir: true

  editURL: "https://gitlab.com/GROUP/PROJECT/tree/master/docs/content/"
  description: "Description of project docs site"
  author: "Consensus Enterprises"
  showVisitedLinks: true
  disableBreadcrumb: false
  disableNextPrev: false
  disableSearch: false
  disableAssetsBusting: false
  disableInlineCopyToClipBoard: false
  disableShortcutsTitle: false
  disableLanguageSwitchingButton: false
  ordersectionsby: "weight" # or "title"

    - name: "<i class='fa fa-gitlab'></i> Gitlab repo"
      url: "https://gitlab.com/GROUP/PROJECT"
      weight: 10
    - name: "<i class='fa fa-bullhorn'></i> Contributors"
      url: "https://gitlab.com/GROUP/PROJECT/graphs/master"
      weight: 30

# For search functionality
    - "HTML"
    - "RSS"
    - "JSON"

Now, remove the original config.toml and add the new config.yaml to git:

git rm config.toml
git add config.yaml
git commit -m "Configure learn theme for docs site"

One extra thing we’ll set up is to enable site-wide search of the docs. This is accomplished by adding a JSON index of the entire site, described in the layouts folder. Create the file docs/layouts/index.json:

[{{ range $index, $page := .Site.Pages }}
{{- if ne $page.Type "json" -}}
{{- if and $index (gt $index 0) -}},{{- end }}
        "uri": "{{ $page.Permalink }}",
        "title": "{{ htmlEscape $page.Title}}",
        "tags": [{{ range $tindex, $tag := $page.Params.tags }}{{ if $tindex }}, {{ end }}"{{ $tag| htmlEscape }}"{{ end }}],
        "description": "{{ htmlEscape .Description}}",
        "content": {{$page.Plain | jsonify}}
{{- end -}}
{{- end -}}]

Once again, add this new file to git:

git add layouts/index.json
git commit -m "Add index.json layout for site-wide search index"

Create some content

Now you can start creating some content. Again, these are just simple text files in Markdown format, so you can create and edit them using whatever your favourite text editor may be.

The content/ folder within your Hugo site contains all content posts, and the menu structure will mirror whatever folder hierarchy you develop. To get started quickly, you can use hugo to initialize some posts for you:

  • hugo new _index.md to create the front page
  • hugo new technical/_index.md to create a sub-section
  • hugo new technical/architecture.md to create a sub page

This will set up the file for the post, which includes metadata (what Hugo calls the “front matter”), after which you simply edit it, and add the content. The front matter of a Hugo post is a section at the top of the file containing metadata such as title, date, etc.

By default, Hugo will create new posts in “draft” state, so at minimum you want to edit each new file to “publish” it. Edit the files you created above (content/_index.md and content/technical/architecture.md), adjust the title as needed, set draft: false, and add some content. For example:

title: "Technical Architecture"
date: 2019-11-06T16:58:13-05:00
draft: false

This page describes the technical architecture of the project.

Make sure your new content files are added to git:

git add content
git commit -m "Add some content"

Serve it up (locally)

Now you are ready to see the site in action. One of the great things about locally is it’s trivial to spin up the site on your local machine and see your draft updates live:

  • hugo serve
  • point your browser to http://localhost:1313/PROJECT

By default, Hugo in this mode will auto-refresh your browser page any time you save a change to a content file, so you can actually see your revisions taking effect on an instance of the site as you type. This makes it much easier to manage updating documentation for a project in tandem with building out or making changes to the codebase itself.

As mentioned above, the menu structure for your docs site mirrors the folder hierarchy within the content/ directory. For example, here’s a typical “tree” of content for a docs site:

├── _index.md
├── admin
│   ├── _index.md
│   ├── updating.md
│   └── workflows
│       ├── _index.md
│       ├── taxonomy.md
├── dev
│   ├── architecture
│   │   ├── _index.md
│   │   ├── search_system.md
│   │   └── static_content.md
│   ├── _index.md
│   └── setup
│       ├── dev-workflow.md
│       ├── _index.md
│       └── theme-dev.md
└── testing
    ├── demo.md
    ├── _index.md
    └── writing_tests.md

This would result in a top-level menu like this:

  • Home (/)
  • Admin (/admin/)
  • Development (/dev)
  • Testing (/testing)

The menu titles themselves are determined by the frontmatter in the _index.md files at each level. Typically this matches the title: element, but sometimes you need to override it, in which case you can specify the menuTitle explicitly:

title: Development Workflows using GitLab
menuTitle: Dev Workflows
weight: 30

GitLab Pages

Our final step is to publish the site using GitLab Pages. This is simply a matter of adding a .gitlab-ci.yml at the root of your project with a pages stage that runs hugo to generate static HTML from your new docs/ folder.

Essentially, you tell GitLab CI to use a Hugo container to render the docs/ folder into a set of static HTML/CSS/JS in the public subfolder, and then move that folder into a location from which GitLab Pages can serve it up.

Note: Before you push to CI, it’s important that you set at least the baseURL in your docs/config.yaml file, so it matches where GitLab Pages will place it. See GitLab’s docs on Pages default domain names for details.

Here’s a simple example .gitlab-ci.yml:

  - publish


  stage: publish
  image: jojomi/hugo
  cache: {}
  - hugo -s docs            # Render the Hugo site
  - mv docs/public .        # Move the public folder into place
      - public              # Tell GitLab Pages to serve the public folder
  - master

See the CI/CD for GitLab Pages docs for more information on this, or the full .gitlab-ci.yml reference for all the gory details.

Now you can push your changes up to GitLab, and you should immediately see a CI pipeline kick in to publish your Pages site.

git add .gitlab-ci.yml
git commit -m "Add .gitlab-ci.yml config to publish Pages site"
git push

Go to the project’s Settings > Pages, and you will see a URL where the site is published under the *.gitlab.io domain, and you can also attach your own domain by clicking the New Domain link and following the instructions to point a DNS entry to the GitLab Pages server.

Example repository

A co-worker of mine has created an example repository by following the steps in this post and committing regularly. You can find it at gitlab.com/mparker17/learn-hugo.


Documentation for a technical project is a crucial component for even the most simple cases. Hugo provides an excellent framework to provide documentation housed within the project’s codebase.

It’s easy for developers to edit and update in tandem with their regular development workflow, and review those changes locally before pushing them up alongside code updates. GitLab Pages provides an easy mechanism for publishing the resulting site, and GitLab CI will auto-publish updates with every push.

Check out the Hugo Documentation pages to find out more about how to use Hugo, manage content, tweak the theme templates, and more.

The article How to add a Hugo-based Docs site to your GitLab Project first appeared on the Consensus Enterprises blog.

We've disabled blog comments to prevent spam, but if you have questions or comments about this post, get in touch!