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.
Table of Contents
- 1. Why documentation goes stale so often
- 2. The basic principle: the pages job and the public directory
- 3. Automatically building and publishing MkDocs documentation
- 4. Automatically publishing a PHPDoc-generated API reference
- 5. Preview deployments for documentation changes in merge requests
- 6. Access control for internal documentation
- 7. Modeling multilingual and versioned documentation
- 8. Setting up redirects and custom error pages
- 9. Common pitfalls and how to fix them
- 10. Summary
- 11. FAQ
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.