Static sites¶
zensical-pages and
hugo-pages both build a static site
and deploy it to GitLab Pages, and both split that into the same two-job
shape: a build job that produces a public/ artifact, and a pages job
that deploys it. This very docs site is built by zensical-pages — its
self-include lives in this repo's own root .gitlab-ci.yml.
The shared shape, and the one thing that makes it work¶
Both jobs in each component share the exact same
changes filter, and that's load-bearing, not
incidental: pages needs: the build job's artifact, so if the filters
ever diverged, a push that skipped the build (nothing relevant changed) but
somehow still matched the deploy filter would try to deploy an artifact
that was never produced — a hard failure. With matching filters, a
no-content push to the default branch correctly skips both jobs: nothing
changed, so nothing rebuilds and nothing redeploys.
zensical-build (unlike hugo-build) doesn't run on tag pipelines at all
— this repo, and consumers like it, use tags for release publishing, not
for a docs redeploy.
hugo-pages is the deliberate exception to the schedule rule¶
Every other component in this repo carries a leading schedule → never
guard, so a Renovate pipeline schedule runs
renovate-self and nothing else — see
change-detection and the schedule-scoping decision
this predates. hugo-pages breaks that rule on purpose, in both its
hugo-build and pages jobs: the schedule rule sits before the
change-gated rules, unfiltered, so a nightly schedule always rebuilds and
redeploys regardless of whether the diff would have matched.
The reason is Hugo-specific: HUGO_ENV=production excludes future-dated,
non-draft content from the build until its date actually arrives. A post
scheduled for tomorrow simply isn't in today's build — there's no code
change that "happens" when the clock ticks over, so change-detection has
nothing to detect. A nightly pipeline schedule is the only mechanism that
makes those posts go live on time. The schedule isn't a fallback here — for
Hugo sites with time-bound content, it is the feature.
The MR-time build gate mr_gate restores¶
Before hugo-pages existed, the project's hand-rolled Hugo sites (blog,
dusthole, shutterandstove) each carried a near-identical pages job with
no MR-time build gate at all — a broken Hugo build only surfaced
after it had already landed on the default branch. hugo-build's default
behaviour (mr_gate: true) fixes that: it runs in merge-request pipelines
too, so a broken template or a malformed shortcode fails the MR, not the
deploy.
mr_gate: false opts back into the old, looser model — hugo-build then
runs only where it deploys (on deploy_branch and on schedule), with no
MR/branch build at all. That's the right shape for a project audited
locally before every push rather than gated in CI — a deliberate trade some
consumers make, not a defect.
zensical-pages bootstraps its toolchain at build time; hugo-pages doesn't need to¶
zensical-build pip-installs Zensical from the consumer's python_lock
file at job runtime, inside infra-tools (which provides Python but not
Zensical itself). hugo-build, by contrast, runs on a pinned
hugomods/hugo image that already bundles Hugo (the -git variant also
bundles git, needed for .GitInfo/.Lastmod) — no per-job install step.
The Zensical approach costs a small amount of job time; it also means a
docs-site upgrade is a lockfile bump, not a component image bump. Neither
is strictly better — they reflect how each tool is typically distributed
and versioned upstream.
See also¶
- Reference:
zensical-pages,hugo-pages - Change-detection — the mechanism both components use, and the one exception
hugo-pagescarves out of it. - How-to: Deploy a docs site
- Specs:
2026-06-22-hugo-pages-component-v0.12,2026-06-22-hugo-pages-mr-gate-v0.13