Skip to navigation

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. The pipeline runs entirely in your project with secrets you own, so Fern doesn’t require access to your repository. Fern only needs a repository connection if you use Fern Editor.

Prerequisites

Add a Fern API key to GitLab

1

Generate a Fern API key

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

fern token

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

2

Add the Fern API key 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 API key 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
stages:
- check
- preview_docs
- publish_docs
- cleanup_preview
before_script:
- apt-get update -y
- apt-get install -y curl jq
- curl -sL https://deb.nodesource.com/setup_current.x | bash -
- apt-get install -y nodejs
- npm install -g fern-api
check:
stage: check
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
- if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
script:
- echo "Checking API is valid"
- fern check
preview_docs:
stage: preview_docs
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
script:
- echo "Generating preview for branch $CI_MERGE_REQUEST_SOURCE_BRANCH_NAME..."
- |
OUTPUT=$(fern generate --docs --preview --id "$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME" --force 2>&1) || true
echo "$OUTPUT"
DEMO_URL=$(echo "$OUTPUT" | grep -oE -m1 '(https://[^[:space:]]+-preview-[^[:space:]]+) ' | tr -d ' ')
echo "Preview URL: $DEMO_URL"
- |
if [ -z "$DEMO_URL" ]; then
echo "No preview URL found"
exit 1
fi
curl --location --request POST \
--header "PRIVATE-TOKEN: $REPO_TOKEN" \
--header "Content-Type: application/json" \
--url "https://gitlab.com/api/v4/projects/$CI_MERGE_REQUEST_PROJECT_ID/merge_requests/$CI_MERGE_REQUEST_IID/notes" \
--data-raw "{ \"body\": \"Preview your docs [here]($DEMO_URL)\" }"
publish_docs:
stage: publish_docs
rules:
- if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
script:
- echo "Publishing Docs"
- fern generate --docs
cleanup_preview:
stage: cleanup_preview
rules:
- if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
script:
- echo "Looking up merged MR for commit $CI_COMMIT_SHA..."
- |
MR_INFO=$(curl -sf --header "PRIVATE-TOKEN: $REPO_TOKEN" \
"https://gitlab.com/api/v4/projects/$CI_PROJECT_ID/repository/commits/$CI_COMMIT_SHA/merge_requests") || {
echo "Failed to query MRs for commit — skipping cleanup"
exit 0
}
SOURCE_BRANCH=$(echo "$MR_INFO" | jq -r 'map(select(.state == "merged")) | .[0].source_branch // empty')
if [ -z "$SOURCE_BRANCH" ]; then
echo "No merged MR found for this commit (likely a direct push to main) — skipping cleanup"
exit 0
fi
echo "Deleting preview for branch: $SOURCE_BRANCH"
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.