GitLab Pages for Project Documentation: Publishing Automatically on Every Merge
AI generated
CI/CD
.yml
GitLab · CI/CD · Documentation
GitLab Pages for Project Documentation
Always current, no manual upload

GitLab Pages turns static documentation, whether generated from PHPDoc, MkDocs, or another generator, into a fixed part of the CI pipeline and republishes it automatically on every merge, without anyone having to upload files by hand.

16 min read GitLab Pages MkDocs CI/CD Automation

1. Why documentation goes stale so often

Project documentation almost always goes stale for the same reason: it lives separately from the code, has to be updated manually and published manually, and that second step reliably gets forgotten in everyday work. A developer changes the API, maybe even updates the local markdown file, but forgets to export and upload it to the internal web server. Six months later, new team members rely on documentation that no longer has anything to do with the actual code.

GitLab Pages solves exactly this problem by turning documentation publishing into a regular CI/CD job that runs automatically on every merge into the main branch. Documentation thus becomes a pipeline artifact just like any other, with the same reliability as a build or a test, and the question "is the documentation current?" reduces to "is the latest pipeline green?".

2. The basic principle: the pages job and the public directory

GitLab Pages works on a simple convention: a job with the reserved name pages must produce a directory called public/ as an artifact. Everything in that directory is automatically served under a GitLab-managed URL after a successful pipeline run, typically https://group.gitlab.io/project on GitLab.com or a corresponding subdomain on a self-hosted instance. No separate deploy step and no external web server are needed.

This mechanism is deliberately tool-agnostic: it does not matter whether the public/ directory is generated by MkDocs, Sphinx, Hugo, a PHPDoc generator, or a simple shell script. GitLab only cares about the end result in the specified directory, which makes getting started very low-friction regardless of which documentation tool you choose.

3. Automatically building and publishing MkDocs documentation

For text-based documentation in markdown, MkDocs is a proven choice because it turns a simple directory structure into a searchable, navigable website. The pipeline configuration for it is straightforward: a job installs MkDocs (ideally via a prebuilt Docker image to save installation time), runs mkdocs build, and declares the resulting directory as an artifact for the pages job.

It matters to restrict the job to the relevant branch, usually main or master, so that not every feature branch overwrites the production documentation. It is also advisable to use a rules block instead of the older only/except to formulate the condition clearly and maintainably.


# .gitlab-ci.yml
stages:
  - build
  - deploy

pages:
  stage: deploy
  image: python:3.12-slim
  script:
    - pip install --quiet mkdocs mkdocs-material
    - mkdocs build --site-dir public
  artifacts:
    paths:
      - public
  rules:
    - if: '$CI_COMMIT_BRANCH == "main"'

4. Automatically publishing a PHPDoc-generated API reference

For a PHP project such as a Magento module, the API reference generated from PHPDoc comments is at least as valuable as a hand-written guide, especially for internal libraries with many reused interfaces. Tools like phpDocumentor read the PHPDoc blocks already present in the project (which, per project standard, are maintained for every class and method anyway) and generate a fully linked HTML reference from them, without developers having to put in extra documentation effort.

In the pipeline, this step combines well with the actual Composer build: after composer install, phpdoc run is executed with the target directory public/. For larger codebases, caching phpDocumentor's intermediate results is worthwhile to noticeably reduce build time on repeated pipeline runs.


# .gitlab-ci.yml
pages:
  stage: deploy
  image: phpdoc/phpdoc:3
  script:
    - phpdoc run -d app/code/Mironsoft -t public --title "Mironsoft API Reference"
  artifacts:
    paths:
      - public
  cache:
    key: phpdoc-cache
    paths:
      - .phpdoc/cache
  rules:
    - if: '$CI_COMMIT_BRANCH == "main"'
      changes:
        - app/code/**/*.php

5. Preview deployments for documentation changes in merge requests

A particularly useful feature is merge request pages: instead of making documentation changes visible only after the merge, a job can be configured to generate its own preview URL for every open merge request. Reviewers then see directly in the rendered layout whether a documentation change looks correct, instead of having to mentally render the markdown source in the diff.

Technically, this requires defining an additional job with the path prefix pages: and the appropriate rules condition for merge request pipelines. GitLab then displays the link to the preview directly in the merge request overview, which significantly speeds up the review process for documentation changes and surfaces formatting errors before the merge.


# Additional job for merge request previews
pages:mr-preview:
  stage: deploy
  image: python:3.12-slim
  script:
    - pip install --quiet mkdocs mkdocs-material
    - mkdocs build --site-dir public
  artifacts:
    paths:
      - public
  environment:
    name: review/mr-$CI_MERGE_REQUEST_IID
    url: "$CI_PAGES_URL"
    auto_stop_in: 1 week
  rules:
    - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'

6. Access control for internal documentation

Not all documentation should be publicly accessible, especially internal API references or architecture decisions with sensitive details. GitLab Pages offers access control for this, coupling visibility to project or group membership: only people with at least guest access to the repository can view the published page at all, everyone else is redirected to the GitLab login.

This setting is found under Settings > General > Visibility, project features, permissions > Pages and should be enabled by default for internal documentation, while public open-source documentation is deliberately published without this restriction. Worth knowing: access control on GitLab.com requires both the Pages website and the viewer to meet certain prerequisites, so on self-hosted instances the exact configuration in gitlab.rb should also be checked.

7. Modeling multilingual and versioned documentation

For longer-running projects, the question sooner or later arises whether documentation should be available in parallel for several major versions. A proven solution is to extend the pipeline so it not only builds the current state into public/, but also generates version-specific subdirectories like public/v1/ and public/v2/, controlled via git tags or a dedicated documentation branch per major version.

For multilingual content, the same principle works with language subdirectories like public/de/ and public/en/, with MkDocs directly supporting this structure via the mkdocs-static-i18n plugin. In both cases the underlying mechanism stays the same: the pages job ultimately delivers a complete public/ directory, regardless of how complex the internal structure underneath it is.

8. Setting up redirects and custom error pages

If a documentation structure is renamed or a chapter moved, old, externally linked URLs otherwise lead to a dead end. GitLab Pages supports a _redirects file in the format known from static hosting services, placed in the public/ directory, which enables simple path redirects without server-side logic. For MkDocs projects, this file can be generated automatically from a list of known renames instead of being maintained by hand.

Just as important is a sensible 404.html in the root of public/, which GitLab Pages automatically serves for paths that do not exist. An error page linking back to the homepage and the search function significantly reduces the bounce rate compared to the generic GitLab default error page, which provides no context about the actual documentation at all.

9. Common pitfalls and how to fix them

The most common mistake is a misnamed job: only a job named exactly pages (on newer GitLab versions also with the prefix pages: for multiple parallel Pages jobs) is recognized by GitLab as a Pages deployment. A job named build-docs that does produce a public/ directory but is not named correctly publishes nothing, even if the pipeline runs successfully.

A second common stumbling block is an incorrect path in site-dir or a relative path that does not match the artifacts.paths entry, causing the artifact to be created but uploaded empty or incomplete. The table below summarizes the key configuration points and their most common sources of error.

Configuration point What GitLab expects Common mistake Where to find it
Job name Exactly pages or prefix pages: Different name like build-docs .gitlab-ci.yml
Artifact path Directory public/ Wrong or relative path artifacts.paths
Branch restriction Only main/master publishes to production Missing rules block rules
Access control Coupling visibility to project membership Public by default for internal docs Settings > Pages
MR preview Own URL per open merge request Missing with a plain main job only environment block

Mironsoft

CI/CD pipelines, zero-downtime deployments and release automation

Deployments that run without downtime and without the nail-biting?

We review existing GitLab pipelines for fragile deployment steps and missing safeguards, then build a release process with zero-downtime deployments, automated checks and a rollback you can actually trust in an emergency.

Pipeline Review

Checking an existing .gitlab-ci.yml for fragility, missing stages and security gaps.

Zero-Downtime Deployment

Building symlink releases, health checks and rollback strategies for Magento stores.

CI/CD Automation

Connecting tests, security scans and deployments into one reliable pipeline.

10. Summary

GitLab Pages for Documentation: The Essentials at a Glance

Basic principle

A pages job delivers a public directory, GitLab publishes it automatically.

Tool-agnostic

MkDocs, phpDocumentor, Sphinx, or Hugo all work with the same pattern.

MR preview

Check documentation changes in the rendered layout before merging.

Access control

Couple internal documentation to project membership instead of publishing it publicly.

11. FAQ: GitLab Pages for Documentation: The Essentials at a Glance

1Does the Pages job have to be named exactly 'pages'?
Yes, GitLab only recognizes a job as a Pages deployment if it is named 'pages' or, on newer versions, carries the prefix 'pages:'. Any other name produces an artifact but no published page.
2Can I use GitLab Pages on a self-hosted GitLab instance too?
Yes, GitLab Pages is part of GitLab Community Edition and only needs to be enabled in gitlab.rb and configured with its own domain on a self-hosted instance.
3How long does it take for a change to go live after the merge?
Usually only as long as the pages job itself takes to build and upload the documentation, typically a few minutes, since there is no separate deployment delay.
4Can I use a custom domain instead of the gitlab.io subdomain?
Yes, under Settings > Pages a custom domain with its own SSL certificate can be configured, which is often desired for publicly accessible documentation.
5What happens to old Pages deployments on every new merge?
By default, every successful pages deployment completely replaces the previous state, unless parallel Pages jobs are explicitly configured for versioning or MR previews.
6Does GitLab Pages also work for plain static HTML files without a generator?
Yes, any directory structured like a static website can be provided directly as the public/ artifact, with no need for MkDocs, Sphinx, or similar tools.
7How secure is access control for truly sensitive internal documentation?
Access control reliably protects against unauthenticated or unauthorized users, but it does not replace dedicated encryption for highly sensitive content, which is better kept off Pages entirely and stored in a separately secured system.
8Can I run multiple Pages websites within a single project?
With the pages: prefix, multiple parallel Pages jobs can be defined, for example for main documentation, previews, and archived versions, as long as each job populates its own subdirectory under public/.
9How do I add search to MkDocs documentation?
MkDocs comes with client-side full-text search by default, which requires no additional server since it uses a pregenerated search index in the static output.
10What do I do if the pages job runs successfully but the page shows 404?
Usually an index file is in the wrong subdirectory, for example public/docs/index.html instead of public/index.html, or the access control setting redirects unauthenticated viewers to a login page that gets misinterpreted as a 404.