GitLab CI Pipelines
The homelab repo uses GitLab CI organized by pipeline source. Each
trigger (schedule, merge request, default-branch push, tag) gets its own
aggregator file, and shared job templates live in a hidden _shared/ dir
that consumers include: and extends:.
Entry point
Section titled “Entry point”.gitlab-ci.yml declares workflow rules and conditionally includes one of
three trigger-specific aggregators:
.gitlab-ci.yml└── conditionally includes one of: ├── .gitlab/pipelines/schedule/main.yml # when CI_PIPELINE_SOURCE == "schedule" ├── .gitlab/pipelines/pre-merge/main.yml # when CI_PIPELINE_SOURCE == "merge_request_event" └── .gitlab/pipelines/default/main.yml # default branch or tag (and not a schedule)The three aggregators are mutually exclusive — a given pipeline run
includes exactly one. Each is a thin include: list of per-feature job
files.
Directory layout
Section titled “Directory layout”.gitlab/pipelines/├── _shared/ # hidden job templates (.build-*, .tag-*, .bump-*)│ ├── pdns-image-rebuild.yml│ ├── netbox-image-rebuild.yml│ ├── vault-autoinit-image-rebuild.yml│ ├── paperless-backup-image-rebuild.yml│ └── homelabctl2-image-rebuild.yml├── schedule/ # CI_PIPELINE_SOURCE == "schedule"│ ├── main.yml # includes the per-feature files below│ ├── pdns-rpz-refresh.yml│ ├── pdns-image-rebuild.yml│ ├── netbox-image-rebuild.yml│ ├── vault-autoinit-image-rebuild.yml│ ├── paperless-backup-image-rebuild.yml│ └── homelabctl2-image-rebuild.yml├── default/ # default branch / tags│ ├── main.yml│ ├── pages.yml│ ├── pipam.yml│ ├── pdns-image-rebuild.yml # ← changes-gated on images/container/pdns/**/*│ └── homelabctl2-image-rebuild.yml # ← changes-gated on code/homelabctl2/**/*└── pre-merge/ # merge request events └── main.ymlFiles named <feature>-image-rebuild.yml follow the
image rebuild pattern. The
_shared/ templates are the source of truth; the trigger-specific files
just extend them and add the right rules: and needs: for that
context.
Why two consumers per image?
Section titled “Why two consumers per image?”Most images we maintain are rebuilt in two scenarios:
- Scheduled: a weekly cron rebuilds the image on top of the latest
base layer (OS patches, upstream package updates). Gated by a CI
variable like
RUN_REBUILD_PDNS=trueset on the GitLab schedule. - Changes-gated on default branch: a push to
mainthat touches the image’s source paths triggers an immediate rebuild. Uses GitLab’srules: - changes:matcher.
Both end up calling the same _shared/ templates so the build / tag /
bump-manifest logic stays identical. The only difference is the trigger
rule and (for pdns, where two images share a directory tree) per-image
changes: paths so that editing only the recursor doesn’t force an
authoritative rebuild.
| Image | Schedule wired in schedule/main.yml | Default wired in default/main.yml |
|---|---|---|
pdns-recursor / pdns-authoritative | ✓ | ✓ |
homelabctl2 | ✓ | ✓ |
netbox | ✓ | file exists, not wired |
vault-autoinit | ✓ | file exists, not wired |
paperless-backup | ✓ | file exists, not wired |
The non-wired default consumers are ready to enable — just add a
- local: ... line to default/main.yml when the image change cadence
justifies it.
Shared shell scripts
Section titled “Shared shell scripts”scripts/ci/rebuild/ holds the shell that all _shared/ image-rebuild
templates call into:
| Script | Purpose |
|---|---|
kaniko-auth.sh | Writes /kaniko/.docker/config.json from $CI_REGISTRY_USER / $CI_REGISTRY_PASSWORD. |
tag-image.sh | Installs crane, computes the <version> / <version>-N tag, retags staging → final + latest, emits TAG_* via dotenv. Accepts VERSION= or VERSION_FROM_ROOTFS=. |
bump-manifest.sh | git-config + branch + sed-patches + commit + MR push. Takes positional pairs of <file> <sed-script>. |
Centralising these means a fix to e.g. crane install or MR push options ships to every image rebuild at once.
Scheduled triggers (CI variables)
Section titled “Scheduled triggers (CI variables)”| Variable | Pipeline |
|---|---|
RUN_REFRESH_RPZS=true | Refresh pdns RPZ blocklists |
RUN_REBUILD_PDNS=true | Rebuild both pdns images (matrix) |
RUN_REBUILD_NETBOX=true | Rebuild netbox |
RUN_REBUILD_VAULT_AUTOINIT=true | Rebuild vault-autoinit |
RUN_REBUILD_PAPERLESS_BACKUP=true | Rebuild paperless-backup |
RUN_REBUILD_HOMELABCTL2=true | Rebuild homelabctl2 |
Managing the schedules
Section titled “Managing the schedules”GitLab pipeline schedules are not click-opsed — they’re reconciled
from an in-source declarative list by
homelabctl2 gitlab schedules sync.
The source of truth is var schedules in
code/homelabctl2/cmd/homelabctl2/gitlab/schedules/schedules.go.
Workflow when adding or changing cadence:
- Edit the slice (description, cron in UTC, gating CI variable, value).
- Run a dry-run to preview:
Terminal window homelabctl2 gitlab schedules sync - Commit, then apply:
Terminal window homelabctl2 gitlab schedules sync --apply
The reconciler only touches rows prefixed with [homelab-bot] in the
GitLab UI description and never deletes — dropping a row from the source
list leaves the GitLab schedule disabled-via-UI as a manual step. See the
reconciler reference for
the full behavior matrix and auth options ($GITLAB_TOKEN or the op
1Password CLI).