How do you deploy a static website using GitLab Pages and what are the configuration requirements in .gitlab-ci.yml?
Quick Answer
GitLab Pages hosts static websites directly from a GitLab repository. You configure a job named pages that produces built files in a public/ directory as an artifact. When the job succeeds on the default branch, GitLab automatically publishes the site at namespace.gitlab.io/project-name with optional custom domain support.
Detailed Answer
Think of GitLab Pages like a display window at a bookstore. You write your book (static site content), hand it to the store clerk (the pages job), and they place it in the display window (GitLab Pages hosting). The display window has a standard address on the street (namespace.gitlab.io/project), but you can also put your own custom sign (custom domain) on the window. The content in the window updates automatically every time you submit a revised manuscript (push to the default branch).
GitLab Pages is a free static site hosting service built into GitLab. It requires a job named exactly pages in your .gitlab-ci.yml that produces an artifact with a directory named exactly public. When this job succeeds on the default branch (usually main), GitLab takes the contents of the public directory and serves them as a static website. The site is available at https://<namespace>.gitlab.io/<project-name> for project-level Pages, or https://<namespace>.gitlab.io for a special project named <namespace>.gitlab.io. GitLab Pages supports any static site generator (Hugo, Jekyll, Gatsby, Next.js static export, MkDocs, Sphinx) or plain HTML/CSS/JS. Custom domains and SSL certificates are supported through the project settings, and GitLab can automatically provision Let's Encrypt certificates for custom domains.
Internally, when the pages job completes, the GitLab CI/CD system detects that the job name is pages and that it has a public directory in its artifacts. It triggers the Pages deployment process, which extracts the public directory from the artifact archive and stores the files in the Pages storage backend (local filesystem or object storage). The GitLab Pages daemon serves these files through a dedicated NGINX or custom HTTP server that handles routing, custom domains, and TLS termination. For custom domains, you add a CNAME or A record in your DNS pointing to the GitLab Pages IP, and configure the domain in the project settings (Settings > Pages > New Domain). GitLab validates domain ownership through a DNS TXT record and can automatically provision and renew Let's Encrypt certificates. The Pages daemon routes incoming requests to the correct project's files based on the domain and path.
In production, teams use GitLab Pages for various purposes beyond simple websites. An API platform team hosting developer-docs publishes their API documentation generated by Swagger UI, with every commit to main triggering a rebuild. A design system team hosts their Storybook component library as a Pages site, giving designers and frontend developers a live reference. An SRE team publishes their runbooks generated from Markdown by MkDocs, ensuring operational documentation is always up to date. For more complex setups, teams configure Pages deployment to run only when documentation files change using the rules keyword with changes detection on docs/ or content/ directories. Access control for Pages sites is available on GitLab Premium, allowing you to restrict access to project members only rather than making the site public.
A common gotcha is the artifact directory name: it must be exactly public, not build, dist, or output. If your static site generator outputs to a different directory (like Hugo's public or Gatsby's public), you are fine, but if it outputs to dist (like Vite or Angular), you need to either configure the generator to output to public or add a cp -r dist public step. Another mistake is naming the job anything other than pages, such as deploy-pages or publish-site, which will not trigger the Pages deployment process. The job must be named pages exactly. Also, Pages deployment only triggers on the default branch by default; if you want to deploy from a different branch, you need to configure that in the project Pages settings.