Skip to content

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:.

.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.

.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.yml

Files 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.

Most images we maintain are rebuilt in two scenarios:

  1. 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=true set on the GitLab schedule.
  2. Changes-gated on default branch: a push to main that touches the image’s source paths triggers an immediate rebuild. Uses GitLab’s rules: - 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.

ImageSchedule wired in schedule/main.ymlDefault wired in default/main.yml
pdns-recursor / pdns-authoritative
homelabctl2
netboxfile exists, not wired
vault-autoinitfile exists, not wired
paperless-backupfile 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.

scripts/ci/rebuild/ holds the shell that all _shared/ image-rebuild templates call into:

ScriptPurpose
kaniko-auth.shWrites /kaniko/.docker/config.json from $CI_REGISTRY_USER / $CI_REGISTRY_PASSWORD.
tag-image.shInstalls crane, computes the <version> / <version>-N tag, retags staging → final + latest, emits TAG_* via dotenv. Accepts VERSION= or VERSION_FROM_ROOTFS=.
bump-manifest.shgit-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.

VariablePipeline
RUN_REFRESH_RPZS=trueRefresh pdns RPZ blocklists
RUN_REBUILD_PDNS=trueRebuild both pdns images (matrix)
RUN_REBUILD_NETBOX=trueRebuild netbox
RUN_REBUILD_VAULT_AUTOINIT=trueRebuild vault-autoinit
RUN_REBUILD_PAPERLESS_BACKUP=trueRebuild paperless-backup
RUN_REBUILD_HOMELABCTL2=trueRebuild homelabctl2

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:

  1. Edit the slice (description, cron in UTC, gating CI variable, value).
  2. Run a dry-run to preview:
    Terminal window
    homelabctl2 gitlab schedules sync
  3. 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).