47

I am deploying my React app using GitLab Pages, and it works well.

Here is my gitlab-ci.yml:

# Using the node alpine image to build the React app
image: node:alpine

# Announce the URL as per CRA docs
# https://github.com/facebook/create-react-app/blob/master/packages/react-scripts/template/README.md#advanced-configuration
variables:
  PUBLIC_URL: /
# Cache node modules - speeds up future builds
cache:
  paths:
  - client/node_modules

# Name the stages involved in the pipeline
stages:
  - deploy

# Job name for gitlab to recognise this results in assets for Gitlab Pages
# https://docs.gitlab.com/ee/user/project/pages/introduction.html#gitlab-pages-requirements
pages:
  stage: deploy
  script:
    - cd client
    - npm install # Install all dependencies
    - npm run build --prod # Build for prod
    - cp public/index.html public/404.html # Not necessary, but helps with https://medium.com/@pshrmn/demystifying-single-page-applications-3068d0555d46
    - mv public _public # CRA and gitlab pages both use the public folder. Only do this in a build pipeline.
    - mv build ../public # Move build files to public dir for Gitlab Pages
  artifacts:
    paths:
    - public # The built files for Gitlab Pages to serve
  only:
    - master # Only run on master branch

Now, I just created a dev version, based on my branch develop

I would like to have 2 versions of my React app with 2 different URLs. How can I do that?

For example right now, I have:

my-react-app.com linked to master branch

How should I have

dev.my-react-app.com or even my-react-app.gitlab.io linked to develop branch?<

timlyo
  • 2,086
  • 1
  • 23
  • 35
Juliatzin
  • 18,455
  • 40
  • 166
  • 325

5 Answers5

39

I've had success using the browsable artifacts for this purpose. In your example, you would create a job for your develop branch and set the PUBLIC_URL to the path on gitlab.io where the job's artifacts are published:

develop:
    artifacts:
        paths:
          - public

    environment:
        name: Develop
        url: "https://$CI_PROJECT_NAMESPACE.gitlab.io/-/$CI_PROJECT_NAME/-/jobs/$CI_JOB_ID/artifacts/public/index.html"

    script: |
        # whatever

    stage: deploy

    variables:
        PUBLIC_URL: "/-/$CI_PROJECT_NAME/-/jobs/$CI_JOB_ID/artifacts/public"

Setting the environment as indicated produces a »Review app« link in relevant merge requests, allowing you to get to the artifacts with a single click.

Note: if your repository is in a subgroup, you need to insert the subgroup name in two places above above between /-/ and $CI_PROJECT_NAME for the resulting URLs to work.

joki
  • 6,619
  • 2
  • 22
  • 30
  • I'm not sure that this works in all cases, particularly when the repo is in a subgroup, which changes the pattern of the `$CI_ENVIRONMENT_URL`. See [this question](https://stackoverflow.com/questions/60530676/can-gitlab-pages-be-used-for-review-apps-on-a-mkdocs-project) for more detail. – snim2 Mar 07 '20 at 09:26
  • can you share a public repo containing this? – Karl Anthony Baluyot Sep 07 '20 at 16:14
  • 1
    @KarlAnthonyBaluyot this [minimal repo](https://gitlab.com/snim2-test-group/snim2-test-subgroup/review-app-tester) by a previous commenter uses the technique, albeit slightly modified for a project in a subgroup. Does this help you? – joki Sep 08 '20 at 18:14
  • 1
    Yes thanks @joki. Have you tried using custom domain for the develop branch gitlab page? – Karl Anthony Baluyot Sep 09 '20 at 03:01
  • 1
    @KarlAnthonyBaluyot I haven't tried it, but it should be fairly straightforward by replacing `gitlab.io` by your custom domain in `environment.url` and configuring your page generator for your custom domain. – joki Sep 10 '20 at 07:08
  • 2
    The documentation for this feature is at https://docs.gitlab.com/ee/ci/yaml/#environmenturl – Étienne Jul 23 '21 at 08:50
  • @joki that was an excellent suggestion - that repo is great - thanks! – Jesse Sep 14 '21 at 15:23
  • 3
    You should be able to use `${CI_SERVER_PROTOCOL}://${CI_PROJECT_ROOT_NAMESPACE}.${CI_PAGES_DOMAIN}/-/${CI_PROJECT_PATH#"${CI_PROJECT_ROOT_NAMESPACE}/"}/-/jobs/$CI_JOB_ID/artifacts/public/index.html` to make the URL independant from your Pages Domain and if the project is in a subgroup or not. – F.Raab May 31 '22 at 08:53
  • @F.Raab I cannot make this working in the `environment.url` of my `gitlab-ci.yml` since there is no variable substitution happening here (I think). – ddidier Jun 29 '22 at 15:06
  • 1
    @ddidier Yea you might be correct, that variable substitution does not happen makes sense. I am using this somewhere in the `script` section and actually did not test it on the environment.url field. Mhmm, I looked closely through the predefined gitlab varibles and unfortunatly did not found anything which yields the same result as `${CI_PROJECT_PATH#"${CI_PROJECT_ROOT_NAMESPACE}/"}`. – F.Raab Jun 30 '22 at 17:12
  • 1
    Is there a way to avoid specifying the `index.html` at the end of the URL? – CDuv Aug 06 '22 at 20:14
  • indeed, it's kinda broken without specifying the index.html at the end of the URL. In case of work in mkdocs, for instance – Artem Ptushkin Jan 26 '23 at 14:02
28

It is possible to keep several pages published for different pipelines/branches.

To do that you need to copy your pages content (basically test report, or whatever needs to be published) to specific unique directory in public folder. For example, the name of the directory can be the id of pipeline (CI_PIPELINE_ID). So the path to pages sources would be like public/$CI_PIPELINE_ID/.

Then the whole public folder should be defined as artifacts with specific unique name (here again "$CI_PIPELINE_ID" can be used).

Unique name for artifacts is needed to not override the artifacts with the next pipeline execution (if name is not specified, the default name will be taken https://docs.gitlab.com/ee/ci/yaml/#artifactsname).

Then you can access the published report via the link:

https://yourGitlab/yourNamespace/yourProjectName/{CI_PIPELINE_ID}/index.html

, that means you can access all your saved reports by changing the pipeline id.

My example:

stages:
  - publish

cache:
  # Required to keep artifacts from old builds, e.g. from master
  paths:
    - public

pages:
  stage: publish
  script:
    - mkdir -p public/$CI_PIPELINE_ID
    - cp target/site/allure-maven-plugin/* public/$CI_PIPELINE_ID/ -R
  artifacts:
    name: "$CI_PIPELINE_ID"
    paths:
      - public
    expire_in: 5 days
  when: always
Blaise
  • 13,139
  • 9
  • 69
  • 97
Vladyslav Pyrozhok
  • 304
  • 1
  • 4
  • 6
  • 1
    Can you post a link to a GitLab repo where this actually works? It does not seem to work for me: https://gitlab.com/ostrokach/deleteme – ostrokach Nov 18 '19 at 16:52
  • Hi, I cannot post a link to a repo, because it is private. Can you try exactly the same as I proposed (I mean copy the folders structure to public)? Because I see that you do it in other way – Vladyslav Pyrozhok Nov 19 '19 at 12:27
  • Hi VladyslavPyrozhok, thanks for your contribution! I tried your example, based on @ostrokach code, but didn't give expected result. Each time I'll run the pipeline, my previously deployed file won't be present anymore. Have a look : https://gitlab.com/alvaro.costa/deleteme/blob/master/.gitlab-ci.yml – Alvaro Costa Nov 19 '19 at 19:59
  • 1
    Sorry guys, I just missed one small piece of code in pipeline file: ```cache: paths: - public``` So, please, add it and I hope it will work. I tested in my repo here [https://gitlab.com/pyrozhok.vladyslav/pages-test](https://gitlab.com/pyrozhok.vladyslav/pages-test) , so you can check. The results are [report_1](https://pyrozhok.vladyslav.gitlab.io/pages-test/97941151/index.html), [report_2](https://pyrozhok.vladyslav.gitlab.io/pages-test/97940959/index.html) . I updated the response. – Vladyslav Pyrozhok Nov 22 '19 at 18:25
  • Also make sure, that you run several pipelines with the same gitlab-ci file – Vladyslav Pyrozhok Nov 22 '19 at 18:29
  • Did you have a chance to check? @alvaro-costa – Vladyslav Pyrozhok Dec 12 '19 at 14:03
  • Sorry Vladyslav, I didn't see you answered until you tag me. :-( – Alvaro Costa Dec 12 '19 at 19:05
  • 7
    About your solution, it indeed works by making a clever use of GitLab cache... but it's a dirty solution since it isn't based on what's already available on GitLab page. If your cache is cleaned or expires (enterprises usually put an expiry time), you lose everything.. I'd suggest looking at [joki's solution](https://stackoverflow.com/a/58402821/2015305) instead. :) – Alvaro Costa Dec 12 '19 at 19:09
  • 1
    @alvaro-costa If your project is public and you can publish your results to the world then it will work. Otherwise if you cannot open your web-page to the internet (like in my case) so it is not a handy solution. Anyway, choose whatever fits you better :) – Vladyslav Pyrozhok Dec 13 '19 at 12:03
13

Every GitLab project can have at most one Pages site. I can't find an explicit reference for this, but the documentation for .gitlab-ci.yml says:

Be aware that Pages are by default branch/tag agnostic and their deployment relies solely on what you specify in .gitlab-ci.yml. If you don’t limit the pages job with the only parameter, whenever a new commit is pushed to whatever branch or tag, the Pages will be overwritten.

Without the only parameter, updates to any branch publish to the same Pages site, overwriting whatever is there. With the only parameter, only the provided branch will trigger a Pages build.

ChrisGPT was on strike
  • 127,765
  • 105
  • 273
  • 257
  • This is really important. I just ran into the issue where the production version from the master branch was *replaced* by a job on a branch. I'm not sure what changed; I used to be able to access the master branch results on `https://[…]/` and the other branch results on `https://[…]/branch-name` with a single job deploying to the same environment. – l0b0 Sep 22 '22 at 14:15
0

Other answers are not fully satisfying :

  • relying on artifacts make the address complex and changing
  • caching in folders based in pipeline ID has the same problems plus never ending size increase (no cleaning)

Using cache is the right direction, but the solution needs more side features.

With below solution, from GitLab Pages per branch : the no-compromise hack to serve preview pages, you can have these 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 keyword)
workflow:
  rules: # disable tag pipelines and duplicate MR pipelines
    - if: $CI_COMMIT_BRANCH

variables:
  EPHEMERAL_BRANCHES_PATH: preview # subpath to ephemeral branches content for preview, anything will work

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" ] && [ ! -d public/$CI_DEFAULT_BRANCH ]; 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 build-docs || true # insert here your code that generates documentation
    - mv --verbose build-docs public/$CURRENT_CONTENT_PATH
    - 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: "."
    # 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: preview/$CI_COMMIT_BRANCH # an indirection to allow arbirtraty 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
NeVraX
  • 173
  • 1
  • 6
-1

I faced this problem as well and first found the gitlab-versioned-pages python package. This works for some time until the pages size and amount of files increases above the GitLab Pages size limit. Sure you can increase that limit, but that does not scale or is sufficient. Same for copying new content into a specific folder, like proposed above with

cp target/site/allure-maven-plugin/* public/$CI_PIPELINE_ID/ -R

I therefore created a the lightweight-versioned-gitlab-pages package, mirrored to Github. Instead of copying or downloading the artifacts and archiving it again until infinity, the script generates a index page with "just" the URLs to the (already) archived artifacts. No dublicated content, lightweight, scalable and you can serve whatever content you want, from any job that ran on any branch. You can use it on selfhosted GitLab instances and of course on GitLab.

The docs for that package are provided via GitLab pages using the package itself. The root page is not beautiful, but it's just a template that can be customized as required.

The only limit or requirement is to have this job running on a tag. Just try it out, report issues or ask for further details