↓Skip to main content

What My Homelab Pipeline Would Look Like as an Internal Developer Platform

·10 mins
DevOps Platform Engineering GitOps Homelab Devops Platform-Engineering Golden-Paths Internal-Developer-Platform Gitops Kargo Argo-Rollouts
Chrisando Wagner
Author
Chrisando Wagner
25+ years across the full estate — Microsoft/VMware, OpenStack/Red Hat, cloud-native Kubernetes. These days that’s reference architectures, FinOps governance, and regulatory guardrails other teams build against. When I’m not debating gateway API controllers, I’m automating something in my homelab.
68 - This article is part of a series.

I’ve been building toward this over the last couple of weeks — writing about how platform engineering split off from DevOps as a discipline, and about the difference between “we all own operations” as a culture and a team whose actual job is building the platform other teams stand on. This post is where I stop talking about that distinction in the abstract and point it at something I’ve actually built.

I run a full GitOps pipeline for this blog. I’ve written it up twice already, in How This Blog Is Served and Progressive Delivery with Kargo — Gitea webhook, Argo Events, Argo Workflows building and scanning, Kargo promoting through a Warehouse/Freight/Stage model, Argo Rollouts cutting a blue-green traffic switch after Playwright and k6 verification. It’s a real pipeline, not a diagram I drew for a conference talk. It also serves exactly one workload, owned by exactly one person, with zero onboarding process, because the only person who’s ever going to onboard onto it is me.

So here’s the thought experiment. If another team at Ebucks — or another project of mine — wanted to ship through something shaped like this pipeline tomorrow, what would actually have to change? Not “how would I rewrite it to be generic,” because that’s the wrong question and I think it’s the question that sinks most internal platform efforts before they ship anything. The right question, the one platform engineering as a discipline is actually built around, is narrower: which parts of this are already a paved road, and which parts are still me, personally, hard-coding a decision that a different team would need to make differently?

Why This Is the Right Question
#

The common advice on building an internal developer platform — and it’s advice I think is correct, not just repeatable — is to start from one real, repeated request, not a green-field platform design. Map the workflow that already exists, work out which parts of it are always done the same way regardless of who’s asking, and turn those parts into something self-service. You don’t design a golden path by imagining what a platform should look like in general. You design it by looking at a specific pipeline you already run and asking which decisions in it were never actually specific to the thing being deployed.

That’s a different exercise from the one I did in an earlier post on this pipeline, where I was checking whether the deploy step itself had become boring. This one assumes the deploy step is fine and asks a harder question: is the pipeline fine, or is it fine because I’m the only person who has to understand all of it?

Walking the Pipeline, Stage by Stage
#

Here’s the real flow again, this time annotated for what’s already a fixed, reusable “paved road” versus what’s currently a decision baked into YAML that only I know exists.

flowchart TD A["Gitea webhook
(paved road)"] --> B["Argo Events
HMAC + filter
(paved road)"] B --> C["Argo Workflows
build + SonarQube + Trivy
(paved road, per-repo config)"] C --> D["Kargo Warehouse
watches one registry path
(per-team variable, hardcoded today)"] D --> E["Kargo Freight + Stage model
(paved road as a pattern)"] E --> F["Promotion template
git-clone/set-image/commit/push
(paved road, but repo path is hardcoded)"] F --> G["AnalysisTemplate
Playwright + k6
(reusable pattern, per-team test content)"] G --> H["Argo Rollouts blue-green
(paved road)"] H --> I["Human traffic cut
kubectl argo rollouts promote
(deliberate, stays manual by design)"] classDef paved fill:#1a4d2e,color:#fff; classDef variable fill:#7a3b12,color:#fff; class A,B,H,I paved class D,F variable class C,E,G paved

Green is stuff I’d hand another team as-is, or close to it. Amber is stuff that currently only works because there’s one Warehouse, one Git repo, one me.

Walking it stage by stage against the actual manifests:

Gitea webhook → Argo Events. The EventSource validates an HMAC signature and the Sensor filters on refs/heads/main. Nothing in that layer knows or cares what’s being built. This is genuinely paved road already — the same EventSource and Sensor shape would work for any repo that fires the same webhook payload shape. What’s missing isn’t logic, it’s multiplicity: right now there’s one EventSource wired to one repo’s webhook URL. Making it a platform primitive means the EventSource (or a template that stamps one out) has to exist per-repo or per-team without me hand-writing the Sensor filter each time.

Argo Workflows — build, SonarQube, Trivy. The blog-build WorkflowTemplate is close to reusable as a pattern — clone, BuildKit multi-arch build, SonarQube quality gate, Trivy CVE scan failing on CRITICAL — but it currently has this blog’s Dockerfile path, this blog’s SonarQube project key, and this blog’s image tag format (vYYYY.MMDDhhmm) baked in as literal values. Turning this into a golden path means promoting those from hardcoded strings to WorkflowTemplate parameters, and deciding — as a platform team, not as me — whether the tag format, the CVE severity threshold, and the SonarQube gate are things every team gets identically (I’d argue yes, that’s exactly the kind of thing that should be a fixed rule, not a per-team knob) or things a team can tune.

Kargo Warehouse. This is the one stage that’s almost entirely bespoke right now, and it’s worth being honest about why. My Warehouse watches a single OCI registry path with a single tag regex. A platform version needs a Warehouse per team or per service, which is fine — Warehouses are cheap, namespaced Kargo resources — but “fine to create” and “self-service to create” are different things. Right now creating one means me writing YAML by hand. A golden path means a template (a WorkflowTemplate-driven scaffold, or at minimum a documented Kustomize base) that a team can instantiate by filling in a registry path and a tag pattern, not by copying my kargo.yaml and hoping they don’t miss a field.

Kargo Freight/Stage/Promotion model. This is the strongest candidate for “already platform-shaped” in the whole pipeline, and it’s worth saying plainly: the abstraction Kargo gives you — Warehouse discovers artefacts, Freight is the immutable unit of promotion, Stages declare what Freight they accept and how they verify it, sources: {stages: [preview]} enforces that production can only ever receive Freight that already passed through preview — is already a templatable, per-team pattern. It doesn’t need redesigning to be a golden path. It needs the promotion template’s steps (which today hardcode deployment/blog/kustomization.yaml as the file Kargo edits) parameterised per-repo, and it needs the two gotchas I hit wiring it up — AutoPromotionEnabled living on a separate ProjectConfig resource, and the Argo CD authorized-stage annotation silently mis-parsing a comma-separated value — turned into documented defaults a new team doesn’t have to rediscover the hard way. That second part is not a small thing. A golden path that reproduces my own debugging session for every new team that adopts it has failed at the one job it exists to do.

AnalysisTemplate — Playwright + k6. The pattern (Kargo triggers an AnalysisTemplate, the Stage’s health tracks the result separately from whether the Promotion itself succeeded) is reusable wholesale. What isn’t reusable is the content of tests/playwright/blog.spec.ts — that’s specific to a Hugo site’s DOM. A platform team offering this as a golden path isn’t writing everyone’s Playwright suite. It’s offering the harness: here’s how you wire a test suite into the verification step, here’s the k6 threshold format we standardise on (p95 under some agreed number), here’s where your test file lives and what contract it has to honour to report back to Kargo correctly. The test content stays a legitimate per-team variable. The harness and the pass/fail contract do not.

Argo Rollouts blue-green, and the human traffic cut. Both of these stay exactly as they are. Blue-green with a blog-active/blog-preview Service split is a Rollout pattern, not a blog-specific one — any team’s Rollout spec looks structurally identical with a different image and different Service names. And the final kubectl argo rollouts promote staying a deliberate human action isn’t a gap to close. It’s the one decision in this entire pipeline that a platform team should be actively defending against automation pressure, not templating away.

The Table Version
#

StageReusable as-is?What it’d take to make it a golden path
Gitea webhook → Argo EventsMostlyTemplate the EventSource/Sensor pair per repo instead of one hand-wired instance
Argo Workflows build + scanPattern yes, values noParameterise Dockerfile path, SonarQube project key, tag format; decide what’s fixed vs tunable
Kargo WarehouseNoPer-team Warehouse via a scaffolded template, not hand-written YAML
Kargo Freight/Stage/Promotion modelYes, this is the platform primitiveParameterise the promotion template’s file path; document the two gotchas as defaults, not tribal knowledge
AnalysisTemplate verificationHarness yes, test content noShip the harness and pass/fail contract; leave test content to the team
Argo Rollouts blue-greenYesNone — structurally identical per team already
Human traffic cutYes, deliberatelyNone — this is a control, not a gap

What’s Actually Missing: Not YAML, Visibility
#

There’s a layer this pipeline doesn’t have at all, and it’s the one platform engineering as a discipline treats as the actual deliverable, not an afterthought: a way for a developer to see where their change is without reading raw ArgoCD and Kargo UI, and without knowing that Kargo owns promotion while ArgoCD owns sync and Argo Rollouts owns the traffic cut. I know that division of responsibility because I built it. A team onboarding onto this platform shouldn’t have to learn three tools’ worth of internals just to answer “did my change reach preview yet, and why not.” That’s the actual product a platform team ships — not the Kargo Stage definitions, which are infrastructure, but the answer to that one question, surfaced somewhere a developer would actually look.

I don’t have that layer today, and I want to be honest about why: I’ve never needed it, because I am simultaneously the only developer and the only platform operator on this pipeline. That’s precisely the gap a real internal developer platform exists to close, and it’s also precisely the kind of gap you can’t see from inside a one-person pipeline no matter how well-built the rest of it is. The mechanics being solid doesn’t mean the platform is done. It means the hard part — deciding what other teams get to configure, and building the thing that tells them what happened when they did — hasn’t started yet.

The Actual Conclusion
#

A golden path is not “take my pipeline and make it generic.” Genericising everything is what produces the internal platforms nobody wants to use — configurable in every dimension, opinionated in none, and slower to onboard onto than just writing your own YAML would have been. What this pipeline taught me, walking it stage by stage with someone else’s onboarding in mind, is that the actual work is smaller and harder than “make it generic”: decide, stage by stage, what’s a fixed paved road a team doesn’t get to argue with — the promotion model, the verification harness contract, the human traffic cut staying human — and what’s a legitimate per-team variable that has to become configuration instead of a value I happened to type into a manifest — the registry path, the test content, the tag format if a team has a real reason to want a different one.

That decision, stage by stage, resource by resource, is the platform engineering work. Not the Kargo install. Not the Argo Workflows template. The judgement about which of those things a team should never have to think about, and which ones they genuinely need control over — that’s the product, and it’s a decision nobody but the platform team can make on a service-by-service basis, because it requires knowing both what the tooling actually does and what the teams actually need, which is exactly the gap a dedicated platform team exists to sit in.

Next week closes out this series with what actually changes in how you think about your own work once you’ve made that shift — from being the person who runs the pipeline to being the person who decides what belongs on the paved road.

Get in touch · View Resume · Connect on LinkedIn

68 - This article is part of a series.