Skip to main content

GitLab Pages are great

Over the past year or so I've been using GitHub Pages more for web-based projects I put on GitHub (and I wish more people deployed their HTML/CSS/JS-only repositories this way concurrently with sharing their raw code!), though I always felt like I wouldn't want to use or promote GitHub Pages for blogging efforts: I don't like to make the meta contents of such projects — the iteration of blog posts, drafts of new posts – public, and public repos are the default for free GitHub accounts.

This blog runs on GitLab Pages, which I only discovered while organising PyDays Vienna (the PyDays website lives on GitLab and uses Sphinx). And GitLab Pages have quickly become a new favourite way of hosting version-controlled static content which can be served via custom (sub)domain(s).

What are Pages used for, anyway?

GitLab Pages and GitHub Pages can be used to present projects — repository contents — in a more user-friendly way. Unlike READMEs, which are more about textual content than visual expression (due to the limitations of Markdown) and often written by developers for other developers, Pages are based on (static) web technologies, thus allow for a greater variety in presentation approaches.

Each instance of Pages is tied to one specific repository (or, to put it differently: each repository can have one instance of Pages) and might contain a project's official website, its documentation, demos or other web content. In the case of websites like this blog, or the PyDays website (or my own experiments with e.g. CSS animations, which I put on GitHub), the online presentation is, in fact, the project.

GitLab and GitHub use separate .io domains to host their respective Pages and allow for the creation of one "top-level" or "overall" instance of Pages per user (or group/team), which can be used as a personal website, or overall project website. In GitLab's case, this top-level instance has the web address (or [1] The repo you use for hosting your account's Pages has to be called just like the subdomain for your account, so if your user name is cameronhowe, you need to create a repository named and use that.

Two neat advantages of GitLab Pages over GitHub Pages

Ignoring the fact that GitLab offers unlimited private repositories for free, GitLab Pages have two great advantages over GitHub Pages: they can be served from any of a repo's branches (unlike on GitHub, where any Pages content needs to live in a branch called gh-pages), which GitLab calls "branch/tag agnostic"; and they support static site generation "on push" thanks to GitLab's own CI (Continuous Integration) and Deployment tooling. Meaning, if you're using them with a static site generator, you don't have to build your files locally and then push the HTML output to your remote, but you can simply push your source files to your repository and they will be built directly on GitLab! [2] [3]

What's needed if you want to use GitLab's CI/CD with a static site generator is a Docker container for your generator (which can be hosted for free directly on GitLab, as part of their "Registry") and a YAML file named .gitlab-ci.yml with build instructions, which has to be put into the root of your repository.

GitLab Pages & Nikola

The static site generator I use for this blog is Nikola, which I found after a longer period of research into (and frustration with other) Python-based static site generators. It comes with both a Docker image and pre-configured YAML file to use with GitLab Pages, which is incredibly helpful as I've yet to look into Docker (though I imagine creating a Docker container is not super difficult once one knows one's way around containering).

Nikola has an example repository up on GitLab whose YAML file you can copy and adapt to your own needs with the help of the project's README and GitLab's docs for .gitlab-ci.yml. The repo is part of an entire collection of example repos for static site generators — I'm sure most major (and possibly also minor) generators either provide their own example project or make similar instructions or pre-configured files available elsewhere.

My .gitlab-ci.yml for looks like this:


  - nikola build
  - mv output public
  - master

    - nikola build
    - mv output public
    - public
  - master
  • the Docker image used is provided by Nikola contributor paddy-hack; I use the latest available version (which might break things, depending on how up-to-date my Nikola version is)
  • pages is the "job" that tells GitLab to create Pages
  • script lists the code which should be run:
    • nikola build generates the blog's HTML contents; it's the command one also uses for building locally
    • mv output public moves the built HTML files to the directory GitLab expects to find them in - Nikola saves them to output by default (which could alternatively also be changed in its config)
  • except/only refer to the branches in the repository in which the files should (not) get built

Building Pages

Here is a screenshot which demonstrates what building GL Pages looks like:

Screenshot of GitLab's *Pipelines* feature building the Pages that are this blog. Find a more detailed description below this image.

Once you've pushed your commits to your remote repository, GitLab will display a tag with a little progress indicator and the word "running" in the CI/CD > Pipelines menu (in GitLab's new, currently still opt-in UI with navigation on the left; in the old layout, the menu is Pipelines > Pipelines). If you load the Pipelines page before GitLab has had a chance to run your scripts, the tag might still say "pending".

When GitLab is done, it reports the outcome of the script run. If it was successful, the tag will change to "passed", but if GL ran into problems creating your Pages, it will read "failed". If the latter happens, it shouldn't necessarily cause worry: GitLab provides an error log, and if your site builds fine locally, a misconfigured YAML file for GitLab's CI might be to blame. I ran into this problem when GitLab could not find a dependency it was expecting — because I'd been unaware that my original .gitlab-ci.yml file needed editing when I installed a new Nikola plugin (the line I had to add was pip3 install rst2html5).

Non-generated HTML

Because I'd been meaning to use a static site generator for such a long time, all my research into GitLab Pages had also focussed on that, and what I found on the topic was certainly satisfactory. But, when I wanted to create a GitLab Pages project using "non-generated" HTML, I was initially at a bit of a loss.

How would I let GitLab know I wanted to use the repository for Pages when I didn't need a GitLab CI YAML file (due to not using a generator waiting for build instructions)? And how would not using such a file go together with specifying a branch from which to read my Pages' HTML contents? (Would GitLab simply default to one particular branch, like master?)

The answer is to include a .gitlab-ci.yml file in your repository anyway, but one that doesn't run any scripts. While it's not immediately obvious when you're not looking for it, there's also an example project for that in the "Pages" group on

What's also really cool about GitLab Pages is that they, like GitHub Pages, can be used with custom domains. Notes on that will follow in a separate blog post.

[1] If you want to use GitLab Pages with any of your "regular" repositories, they are available as subdirectories of your subdomain, following the pattern Care has to be taken when renaming projects, as a repository's name does not necessarily reflect its path. Make sure the path is correct (matches your expectations) in Settings > General > Advanced settings.
[2] GitHub supports automated building, too — and has for a long time, I believe — but only for Jekyll, which I've never been super interested in using because it is Ruby-based and I prefer to work with Python. Also, there are actually more than two advantages to GitLab Pages over GitHub Pages. This comparison page is handy, though seeing as it's hosted on, not without bias (it actually comes with a note also saying so).
[3] GitLab's plans have a limited number of "pipeline minutes" per month, i.e. there is a set amount of available build time for each plan. All build times across all repositories that belong to an account (groups are treated like separate accounts) are counted towards this "pool" of minutes. GitLab's free plan comes with 2000 free pipeline minutes. You can check the quota for your personal account in your settings, and that for any of your groups at (or in Settings > Pipelines quota from the group's main page).