Netlify Deployment Previews for Self-hosted GitLab on Private Network

Written by Vivek Matthew on March 18, 2022; tagged under netlify, gitlab, ci/cd

Netlify provides an integration with GitLab for easily deploying sites on to Netlify with their Webhook integrations. However, using the available integration with a private, self-hosted GitLab instance can be tricky. The integration requires the self-hosted GitLab instance to be open to the internet and so an instance hosted behind a VPN or Firewall will not be able to use this feature. As there are projects where having a GitLab instance accessible over the public internet is not an option, due to organisational security policies, this limitation ends up being a blocker to the use of this feature. In this post, we will go over the setup of a GitLab CI Pipeline for Deploy Previews using the netlify-cli tool and it’s manual deploy feature. With this setup, deployments can be made to Netlify from a GitLab instance hosted behind a VPN or firewall.

Install netlify-cli

On the local development machine, install netlify-cli:

npm install netlify-cli -g

Create a Netlify Site

Create a new Netlify site:

netlify sites:create

Choose a Netlify team to use and provide the site name. Make a note of the Site ID provided by Netlify.

Create a Netlify Personal Access Token

On the Netlify User Settings page, create a Personal Access Token. Copy and save the access token at a secure location.

Add the tokens to GitLab CI settings

Add the saved tokens as NETLIFY_SITE_ID and NETLIFY_AUTH_TOKEN as GitLab CI environment variables on the repository CI/CD settings page.

Setup the GitLab CI Pipeline

Create a .gitlab-ci.yml file in the root of the repository.

image: node:17-alpine3.14

variables:
  NETLIFY_SITE_ID: $NETLIFY_SITE_ID
  NETLIFY_AUTH_TOKEN: $NETLIFY_AUTH_TOKEN

stages:
  - preview

preview:
  stage: preview
  script:
    - npm install
    - apk add --no-cache zip curl
    - npm run build-storybook
    - npm install netlify-cli --save-dev
    - npx netlify deploy --site $NETLIFY_SITE_ID --auth $NETLIFY_AUTH_TOKEN --dir storybook-static/
  artifacts:
    paths:
      - storybook-static
  tags:
    - netlify

For this example, we will be using a storybook project. The above pipeline installs the required npm dependencies, builds a Storybook app locally on the GitLab runner and then deploys it to Netlify. The Deploy Previews on Netlify are persistent so a history of deployments can be kept for future reference.

Cache the npm dependencies

To make the pipeline run a bit faster, setup a cache for .npm in the pipeline:

cache:
  paths:
    - .npm/
  key:
    files:
      - package-lock.json

And use npm ci instead of npm install.

-    - npm install
-    - apk add --no-cache zip curl
-    - npm run build-storybook
-    - npm install netlify-cli --save-dev
+    - npm ci --cache .npm --prefer-offline
+    - apk add --no-cache zip curl
+    - npm run build-storybook

Note that npm ci requires a package-lock.json to be present in the repository. For more details on the intricacies of npm caching on GitLab, refer this answer on StackOverflow.

Prevent GitLab from creating duplicate pipelines

When there is an open MR and code is pushed to the source branch, GitLab creates a merge request pipeline and a branch pipeline, causing duplicate pipelines to be created. To avoid the creation of duplicate pipelines, add the below rules in the preview job, so that preview is run only for merge request pipelines:

  rules:
    - if: $CI_PIPELINE_SOURCE == 'merge_request_event'

When using GitLab 13.8 or newer, the below workflow can instead be used to allow preview to work with branch pipelines as well, without creating duplicate pipelines:

workflow:
  rules:
    - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
    - if: '$CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS'
      when: never
    - if: '$CI_COMMIT_BRANCH'

Update the MR with a deploy status note

Adding a note to the MR with the deploy status and a link to the deploy preview URL makes it easier to keep track of deployments. For setting this up, create a GitLab Project Access Token. Next save the access token as a GITLAB_API_TOKEN variable in the GitLab project settings. In order to send a POST request to the GitLab API for creating the MR note, make the following change to the pipeline.

variables:
+  GITLAB_API_TOKEN: $GITLAB_API_TOKEN

preview:
  script:
-    - npx netlify deploy --site $NETLIFY_SITE_ID --auth $NETLIFY_AUTH_TOKEN --dir storybook-static/
+    - npx netlify deploy --site $NETLIFY_SITE_ID --auth $NETLIFY_AUTH_TOKEN --dir storybook-static/ | tee netlify_output.txt
+    - if [ -z ${CI_MERGE_REQUEST_IID+x} ]; then exit 0; fi
+    - export DRAFT_URL=$(cat netlify_output.txt | grep 'Draft.*https' | sed 's/\[39m//g' | awk -F " " '{print $NF}')
+    - 'curl --request POST
+      --header "PRIVATE-TOKEN: $GITLAB_API_TOKEN"
+      --data-urlencode "body=✔ **Deploy Preview ready for [Pipeline $CI_PIPELINE_IID]($CI_PIPELINE_URL)**
+
+
+
+      🔨 Explore the source changes: $CI_COMMIT_SHA
+
+
+
+      😎 Browse the preview: $DRAFT_URL"
+      "https://$CI_SERVER_HOST/api/v4/projects/$CI_PROJECT_ID/merge_requests/$CI_MERGE_REQUEST_IID/notes"'

Now, whenever the deployment on to Netlify is successful for a merge request pipeline, the pipeline will add a comment to the MR containing a link to the commit diff and the Deploy Preview URL.

Conclusion

With this setup we now have Deploys Previews from a self-hosted GitLab instance, on to Netlify, even when the GitLab instance is inside a private network.

If you have any questions or feedback, feel free to drop us a mail at team@codemancers.com.