CI/CD & GitOps

Pipelines

Work as pipelines, not checklists - with the private-to-public publish pipeline as the centerpiece: a one-way trust boundary that only lets scrubbed content cross from the private source of truth to the public web.

What it is & why I use it

In AutomationLab everything meaningful runs as a pipeline - a defined, repeatable sequence with gates, secrets pulled at run time, and an audit trail - rather than a sequence of manual steps. This page is centered on the one I find most instructive: the publish pipeline that ships this very site from a private repository to the public web. (The provisioning flow that builds VMs is the other big one, sketched below.)

The publish pipeline is interesting because it crosses a trust boundary. Private source and public output are two different worlds, and the pipeline's whole job is to make sure only content that has passed a hard scrub ever moves from the first to the second. That makes it a small, concrete example of a supply-chain control.

How I use it in the lab

  • Publish pipeline (the centerpiece). The private git server is the source of truth; a build job takes the site's public-safe subtree, runs a fail-closed scrub gate over it, and only then pushes it to a public repository that a static host serves.
  • Fail-closed scrub gate. The same publishing checklist that governs the content runs as a pipeline step: if it finds a forbidden pattern (an internal address, hostname, or secret shape), the build fails and nothing is published. The safe default is "do not ship."
  • Secrets fetched, never stored. The push to the public target uses a fine-grained token fetched from the secrets manager at run time and held only in memory - it is never written to disk or baked into a job.
  • Provisioning pipeline (the other one). The lab's build-a-VM flow is also a pipeline: allocate an address, apply infrastructure-as-code, register for monitoring, configure, gate on approval, and enroll - rolling forward on partial failure and reversing cleanly on decommission.

Architecture

The publish pipeline as a one-way trust boundary:

  • Private source of truth. The self-hosted git server holds the full repo, including things that must never be public.
  • Scrub gate as the crossing. The build extracts only the publishable subtree and runs the scrub gate. A single forbidden match aborts the publish - fail closed.
  • Public target, then served. On a clean scrub, the build pushes to a public repository, which a static host builds and serves. Content only ever flows private -> public, never back.
  • Deliberately plugin-light trigger chain. A change notification reaches the build controller through the git plugin's commit-notify endpoint plus a polling backstop, rather than by installing a source-forge-specific controller plugin - fewer plugins is a smaller attack surface for something that holds a publish credential.
Diagram coming Private repo -> build + fail-closed scrub -> push (token from the secrets manager, in memory) -> public repo -> static host. One-way boundary. Scrubbed of the controller's load-balancer address, repo URLs, and secret paths.

In action

Screenshot coming A publish run: scrub gate passing, then a push to the public target and the live site updating. And a failing run where the scrub gate blocks a publish. Captured per the publishing checklist - addresses, repo URLs, and token material redacted.

Lessons learned & gotchas

Gotcha - a green webhook delivery proves nothing about builds The trigger looked perfect: every webhook delivery from the git server showed a successful 200, so I assumed commits were kicking off builds. They were not. The commit-notify endpoint was answering, but its response body said No git jobs using repository - because the job did not have Poll SCM enabled, so nothing had registered as a polling consumer of that repo. A webhook can only nudge jobs that are already watching; with none watching, the nudge is delivered and discarded, and the delivery log is green the whole time. The fix was to enable polling so the job registers as a consumer - and the lasting lesson is that the commit-notify response body is the real diagnostic, not the HTTP status of the delivery.
  • Delivery success is not effect success. "The webhook returned 200" tells you the endpoint was reached, not that a build ran. Verify the thing you actually wanted - a build started - not the hop before it.
  • Fail closed on a publishing gate. A scrub gate that failed open - publishing when the check errored or was skipped - would be worse than none, because it would feel safe. The gate must treat "could not confirm clean" as "do not publish."
  • Prefer fewer plugins on anything that holds a credential. Reaching the trigger through a generic endpoint plus polling, instead of a bespoke plugin, keeps the publish controller's attack surface small - a deliberate security tradeoff, not an accident of setup.
  • Keep the publish token in memory only. A token that pushes to the public world is exactly the thing you never want on disk or in a job definition - fetch it at run time, use it, discard it.

Impact

One-way
private to public
Fail-closed
scrub gate on publish
0
publish tokens on disk

The publish pipeline is what lets a private, internal-detail-heavy repository safely feed a public website: every deploy passes a hard scrub, the credential that crosses the boundary never lands on disk, and the trigger path is kept deliberately simple. The webhook gotcha is the reminder that ties it together - a pipeline is only trustworthy when you verify the outcome you wanted, not the green light one step before it.

Code & further reading

Published pipeline code is scrubbed of the controller's address, repo URLs, secret paths, and any token material before it goes public; the publish token always comes from the secrets manager.