Hosting with GitLab

View as Markdown

Use GitLab CI/CD to automatically generate preview links on merge requests, publish your Fern docs when changes are merged to main, and delete preview links after merge.

Prerequisites

Add a Fern token to GitLab

1

Generate a Fern token

Run fern token in your terminal from the directory containing your fern folder. This generates an organization-scoped token that authenticates the Fern CLI in CI/CD.

$fern token

Copy the token output — you’ll add it to GitLab in the next step.

2

Add the Fern token as a CI/CD variable

  1. Log in to GitLab and navigate to your Fern docs repository.
  2. Go to Settings > CI/CD.
  3. Scroll to the Variables section, select Expand, then click Add variable.
  4. Set the key to FERN_TOKEN, paste the token you generated in the previous step as the value, deselect Protect variable, and click Save changes.

Add a project access token to GitLab

To post preview links on merge requests, you need a GitLab project access token.

1

Create a project access token

  1. In your GitLab repository, go to Settings > Access Tokens.
  2. Click Add new token and configure the following:
    • Token name: a descriptive name (e.g., fern-preview)
    • Expiration date: set as needed (you’ll need to regenerate once it expires)
    • Role: Reporter
    • Scopes: api
  3. Click Create project access token and copy the token.
Save your token

Save the generated token immediately — it won’t be displayed after you leave the page.

2

Add the project access token as a CI/CD variable

  1. Go to Settings > CI/CD.
  2. Scroll to the Variables section, select Expand, then click Add variable.
  3. Set the key to REPO_TOKEN, paste the project access token as the value, deselect Protect variable, and click Save changes.

Add the CI/CD pipeline

Create a .gitlab-ci.yml file in the root of your repository. This pipeline validates your API definition, posts a per-branch preview link on each merge request, publishes your docs when changes are merged to main, and deletes the merged branch’s preview deployment.

.gitlab-ci.yml
1stages:
2 - check
3 - preview_docs
4 - publish_docs
5 - cleanup_preview
6
7before_script:
8 - apt-get update -y
9 - apt-get install -y curl jq
10 - curl -sL https://deb.nodesource.com/setup_current.x | bash -
11 - apt-get install -y nodejs
12 - npm install -g fern-api
13
14check:
15 stage: check
16 rules:
17 - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
18 - if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
19 script:
20 - echo "Checking API is valid"
21 - fern check
22
23preview_docs:
24 stage: preview_docs
25 rules:
26 - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
27 script:
28 - echo "Generating preview for branch $CI_MERGE_REQUEST_SOURCE_BRANCH_NAME..."
29 - |
30 OUTPUT=$(fern generate --docs --preview --id "$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME" --force 2>&1) || true
31 echo "$OUTPUT"
32 DEMO_URL=$(echo "$OUTPUT" | grep -oE -m1 '(https://[^[:space:]]+-preview-[^[:space:]]+) ' | tr -d ' ')
33 echo "Preview URL: $DEMO_URL"
34 - |
35 if [ -z "$DEMO_URL" ]; then
36 echo "No preview URL found"
37 exit 1
38 fi
39 curl --location --request POST \
40 --header "PRIVATE-TOKEN: $REPO_TOKEN" \
41 --header "Content-Type: application/json" \
42 --url "https://gitlab.com/api/v4/projects/$CI_MERGE_REQUEST_PROJECT_ID/merge_requests/$CI_MERGE_REQUEST_IID/notes" \
43 --data-raw "{ \"body\": \"Preview your docs [here]($DEMO_URL)\" }"
44
45publish_docs:
46 stage: publish_docs
47 rules:
48 - if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
49 script:
50 - echo "Publishing Docs"
51 - fern generate --docs
52
53cleanup_preview:
54 stage: cleanup_preview
55 rules:
56 - if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
57 script:
58 - echo "Looking up merged MR for commit $CI_COMMIT_SHA..."
59 - |
60 MR_INFO=$(curl -sf --header "PRIVATE-TOKEN: $REPO_TOKEN" \
61 "https://gitlab.com/api/v4/projects/$CI_PROJECT_ID/repository/commits/$CI_COMMIT_SHA/merge_requests") || {
62 echo "Failed to query MRs for commit — skipping cleanup"
63 exit 0
64 }
65 SOURCE_BRANCH=$(echo "$MR_INFO" | jq -r 'map(select(.state == "merged")) | .[0].source_branch // empty')
66
67 if [ -z "$SOURCE_BRANCH" ]; then
68 echo "No merged MR found for this commit (likely a direct push to main) — skipping cleanup"
69 exit 0
70 fi
71
72 echo "Deleting preview for branch: $SOURCE_BRANCH"
73 fern docs preview delete --id "$SOURCE_BRANCH" || echo "Preview deletion returned non-zero — it may already be gone"

Commit and push the .gitlab-ci.yml file to your repository. The pipeline runs automatically on merge requests and when changes are merged to your default branch.