🦊 GitLab Pages per Branch: The No-Compromise Hack to Serve Preview Pages
Tags: gitlab, hack, devops, cicd
Published: 2023-08-01 06:01:56

---
title: "\U0001F98A GitLab Pages per Branch: The No-Compromise Hack to Serve Preview Pages"
tags:
  - gitlab
  - hack
  - devops
  - cicd
license: public-domain
cover_image: 'https://raw.githubusercontent.com/bcouetil/articles/main/images/gitlab/cyber-pirate/piratepunkai-caribbean-orange-fox-reading-book-tree_blindbox-1.jpg'
published: true
id: 1499346
date: '2023-08-01T06:01:56Z'
---

<!-- (masterpiece, best quality), ((orange PiratePunkAI fox reading a book in a tree)), (piles of books),  <lora:PiratePunkAIV4-000004:1>,  <lora:blindbox_v1_mix:1>, caribbean sea background

Nouveau mois, nouvel article 🤓

Un sujet GitLab très spécifique, en ce mois d'août, traditionnellement très calme: la solution ultime pour permettre des GitLab Pages par branche (ce qui n'est pas possible par défaut).

Le sujet n'intéressera qu'un petit nombre 😅, mais je vous ai mis un thème estival pour rendre ça quand même plaisant 😇

#gitlab #devops #cicd

https://dev.to/zenika/gitlab-pages-preview-the-no-compromise-hack-to-serve-per-branch-pages-5599

-->

{%- # TOC start (generated with https://github.com/derlin/bitdowntoc) -%}

- [Initial thoughts](#initial-thoughts)
- [Prerequisites](#prerequisites)
- [The GitLab pipeline code](#the-gitlab-pipeline-code)
- [Integrated features](#integrated-features)
- [Wrapping up](#wrapping-up)
- [Further reading](#further-reading)

{%- # TOC end -%}

# Initial thoughts

With GitLab Pages, you can publish static websites directly from a repository in GitLab. By default, we cannot have preview pages: if a job deploys the pages, this overwrites previous content, which disallows preview mode.

Some article on the internet show [how to get around that with artifacts](https://k33g.gitlab.io/articles/2020-07-23-GITLAB-PAGES-EN.html), knowing that GitLab can display artifacts. But this trick has disadvantages, mainly highly technical links, that have to be shared again after changes on the branch.

Some [Github gist](https://gist.github.com/donaldpipowitch/2590b20520b2cf6ae01aab4f7b55f8fa) points into the right direction, but in a complex way and with too few side features.

In this article, we will get around the limitation by taking advantage of the cache mechanism, and be able to display per-branch content, with the side benefit of obfuscating the path to ephemeral branches content, if desired.

The solution can be broken down to these steps:

- Generate files for current branch
- Get previous branches generation from GitLab cache
- Merge and update cache
- Auto delete obsolete cache on branch deletion, using GitLab environments

# Prerequisites

We assume you already have a way of generating your HTML static content, and just want to serve the files using GitLab Pages.

For the code to work, your cache must be centralized, either by using gitlab.com runners, by having a single runner, or by sharing caches between multiple private runners.

You need to accept a global cache by deactivating the GiLab option `Use separate caches for protected branches` in **Settings** -> **CICD** -> **General Pipelines**.

![((orange PiratePunkAI fox reading a book in a tree)), (piles of books), <lora:PiratePunkAIV4-000004:1>, <lora:blindbox_v1_mix:1>, caribbean sea background](https://raw.githubusercontent.com/bcouetil/articles/main/images/gitlab/cyber-pirate/piratepunkai-caribbean-orange-fox-reading-book-tree_blindbox-2.jpg)

# The GitLab pipeline code

```yaml
workflow:
  rules: # disable tag pipelines and duplicate MR pipelines
    - if: $CI_COMMIT_BRANCH

variables:
  MAIN_BRANCH_PATH: "."
  EPHEMERAL_BRANCHES_PATH: preview # subpath to ephemeral branches content for preview

pages:
  stage: build
  image: alpine:3.18
  cache:
    key: gitlab-pages
    paths: [public]
  before_script:
    # default available 'tree' app in alpine image does not work as intended
    - apk add tree
    # CURRENT_CONTENT_PATH is defined in rules, different between main branch and ephemeral branches
    - mkdir -p public/$CURRENT_CONTENT_PATH && ls public/$CURRENT_CONTENT_PATH/..
    - | # avoid deleting main branch content when cache has been erased
      if [ "$CI_COMMIT_BRANCH" != "$CI_DEFAULT_BRANCH" ] && [ ! -f public/$MAIN_BRANCH_PATH/index.html ]; then
        echo -e "💥\e[91;1m Unable to retrieve $CI_DEFAULT_BRANCH generated files from cache ; please regenerate $CI_DEFAULT_BRANCH files first\e[0m"
        exit 1
      fi
    - rm -rf public/$CURRENT_CONTENT_PATH || true # remove last version of current branch
  script:
    - ./generate-my-html.sh --output public/$CURRENT_CONTENT_PATH || true # insert here your code that generates documentation
    - cd public/$EPHEMERAL_BRANCHES_PATH
    - tree -d -H '.' -L 1 --noreport --charset utf-8 -T "Versions" -o index.html # generate a root HTML listing all previews for easier access
  environment:
    name: pages/$CI_COMMIT_BRANCH
    action: start
    url: $CI_PAGES_URL/$CURRENT_CONTENT_PATH
    on_stop: pages-clean-preview
  rules:
    # 'main branch' is exposed at GitLab Pages root
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
      variables:
        CURRENT_CONTENT_PATH: $MAIN_BRANCH_PATH
    # other (short-lived) branches generation are exposed in 'EPHEMERAL_BRANCHES_PATH/branch-name-sanitized' sub path
    - variables:
        CURRENT_CONTENT_PATH: $EPHEMERAL_BRANCHES_PATH/$CI_COMMIT_REF_SLUG
  artifacts:
    paths: [public]
    expire_in: 1h

pages-clean-preview:
  stage: build
  image: alpine:3.18
  cache:
    key: gitlab-pages
    paths: [public]
  variables:
    GIT_STRATEGY: none # git files not available after branch deletion
    FOLDER_TO_DELETE: $EPHEMERAL_BRANCHES_PATH/$CI_COMMIT_REF_SLUG # an indirection to allow arbitrary deletion when launching this job
  script:
    - rm -rf public/$FOLDER_TO_DELETE
  environment:
    name: pages/$CI_COMMIT_BRANCH
    action: stop
  rules:
    - if: $CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH
      when: manual
      allow_failure: true
```

![((orange PiratePunkAI fox reading a book in a tree)), (piles of books), <lora:PiratePunkAIV4-000004:1>, <lora:blindbox_v1_mix:1>, caribbean sea background](https://raw.githubusercontent.com/bcouetil/articles/main/images/gitlab/cyber-pirate/piratepunkai-caribbean-orange-fox-reading-book-tree_blindbox-3.jpg)

# Integrated features

The above code has below features:

- `main` content is exposed on `$CI_PAGES_URL` and the path is configurable with `$CURRENT_CONTENT_PATH`
- Per-branch preview content is exposed on `$CI_PAGES_URL/preview`, with a homepage to easily navigate to branches content
- Path to root preview folder is configurable with `$EPHEMERAL_BRANCHES_PATH` variable to hide preview content by obfuscation
- Generated pages are associated with environments to take advantage of auto-cleaning on branch deletion
- To avoid disturbing already existing environments, pages environment are placed under a `pages` folder
- If `main` content has not been generated in current cache, or if the cache has been deleted, an error is triggered, to avoid accidental deletion
- Deletion job can be triggered manually with any cache path as input, to clean outdated data
- Code can safely be added to an existing project pipeline without causing trouble with already existing jobs
- The `workflow:rules` can be deleted if you already have your own, or updated to match your flow
- The job must be named `pages` and the artifact must be a `public` folder to be deployed to GitLab Pages (or you can use the [pages:publish](https://docs.gitlab.com/ee/ci/yaml/#pagespublish) keyword)

# Wrapping up

Given the piece of yaml provided, using it in your pipeline, you should be able to share your GitLab Pages on a per-branch basis, on the path you want, while serving the stable content from the root context.

For any question or remark, please use below comment section 🤓.

If you need information to choose better runner architecture, you can read [GitLab Runners topologies: pros and cons](https://dev.to/zenika/gitlab-runners-topologies-pros-and-cons-2pb1).

![((orange PiratePunkAI fox reading a book in a tree)), (piles of books), <lora:PiratePunkAIV4-000004:1>, <lora:blindbox_v1_mix:1>, caribbean sea background](https://raw.githubusercontent.com/bcouetil/articles/main/images/gitlab/cyber-pirate/piratepunkai-caribbean-orange-fox-reading-book-tree_blindbox-4.jpg)

_Illustrations generated locally by Automatic1111 using RevAnimated model with PiratePunkAI and Blindbox LoRA_

# Further reading

{% embed https://dev.to/zenika/gitlab-a-python-script-displaying-latest-pipelines-in-groups-projects-5b5a %}

{% embed https://dev.to/zenika/gitlab-a-python-script-calculating-dora-metrics-258o %}

{% embed https://dev.to/zenika/gitlab-ci-the-majestic-single-server-runner-1b5b %}

{% embed https://dev.to/zenika/gitlab-ci-yaml-modifications-tackling-the-feedback-loop-problem-4ib1 %}

{% embed https://dev.to/zenika/gitlab-ci-optimization-15-tips-for-faster-pipelines-55al %}

{% embed https://dev.to/zenika/gitlab-ci-10-best-practices-to-avoid-widespread-anti-patterns-2mb5 %}

{% embed https://dev.to/zenika/chatgpt-if-you-please-make-me-a-gitlab-jobs-attributes-sorter-3co3 %}

{% embed https://dev.to/zenika/gitlab-runners-topologies-pros-and-cons-2pb1 %}