← All posts

GitOps Is the Control Plane

Paddy O'Cybear7 min read

There's a question every platform team asks the moment a new deploy tool walks in: does it replace my GitOps, or feed it? Most tools answer wrong. They want to be the control plane — their console, their API, their database of desired state, with your GitOps at best a mirror of it afterward.

Kilter answers the other way. GitOps is the control plane. Kilter is an authoring tier that feeds it. And later this year, kilter apps will promote seamlessly from a kilter-provisioned middle environment into pure-Kubernetes production reconciled by your ArgoCD — through your production engineering process: PRs, AppProjects, RBAC, sync windows, audit, all yours.

This post is the plan.

Two tiers, two needs, one artifact pipeline

Dev/QA and Prod want opposite things from a control plane, and a single tool serving both ends up compromising both.

Dev/QA wants velocity. You spin up an env per PR, tear it down, mutate it, break it. Hand-writing a Namespace, two ServiceAccounts, and ten secrets per ephemeral environment would be madness — you want a controller to do that provisioning for you, instantly, and throw it away. This is kilter's home: an imperative operator that, on kilter deploy, builds and pushes the image, renders the chart, provisions a database and a user, registers a workflow namespace, cuts an object-store bucket, writes per-app secrets — then commits the result and lets Flux take it live. Fast, magical, disposable.

Prod wants trust. You want the deployed state to be exactly what a human reviewed in a pull request, reconciled continuously, with no hidden imperative layer and no machine co-authoring the source of truth. You want pure declarative Kubernetes — manifests that are self-sufficient, diffable, and reconciled by ArgoCD with self-heal and prune behind your RBAC.

One artifact pipeline serves both: the app is an OCI image, the deployment is an OCI chart (kilter-app), the configuration is a known shape of environment and secrets. What changes between the tiers is not the artifact — it's the control style.

The promotion: transmute provisioned into declared

Today, kilter's Dev/QA envs are operator-rendered: the controller commits a HelmRelease but synthesizes the Namespace, ServiceAccounts, and backing secrets at runtime — they're not in git. That's the right tradeoff for an ephemeral middle tier, and the wrong one for prod. (It's the "almost" from the last post — the seam between a delightful delivery layer and a hidden imperative one.)

Promotion is the step that closes the seam. A kilter package takes a frozen middle-tier environment and emits a self-sufficient bundle for prod: every runtime object materialized as a declared one, ready for ArgoCD to reconcile with no kilter-server anywhere in the path.

Here is what is identical across the tiers, and what transmutes on promotion:

Dev/QA (kilter middle)Prod (ArgoCD)
App imageOCI, digest-pinnedsame OCI digest
Chartkilter-app, OCIsame chart, same version
Config shapeenv + secret keyssame keys
Namespacesynthesized (targetNamespace)committed Namespace manifest
ServiceAccountssynthesizedcommitted SA + RoleBinding
ReconcilerFlux Kustomization (operator-committed)ArgoCD Application (PR-reviewed)
Connection secretsinjected at runtimecommitted SOPS (or ExternalSecret)
Provisioned resourcesoperator creates DB/user, workflow ns, bucketdeclarative: CNPG Database, Crossplane claims, a workflow-namespace declaration

The artifacts that matter — image, chart, configuration shape — flow through untouched. Only the control changes: injected becomes declared, operator-push becomes git-pull.

The one real piece of engineering is the bottom row. In Dev/QA, kilter's provisioners create a database and a user, register a workflow namespace, cut a bucket and a key. In prod those same resources must be created declaratively, as objects ArgoCD reconciles: a CloudNativePG Database for the database and user, a Crossplane claim for the object-store bucket, an idempotent workflow-namespace declaration. Kilter knows how to provision these imperatively today; the work is to emit the declarative equivalent — to write the provisioner's intent as a Crossplane or CNPG manifest rather than executing it. Stateless connection secrets (the LLM gateway URL, the tracing endpoint, SMTP) simply get SOPS-encrypted and committed, the same way kilter already encrypts them — it just commits them instead of injecting them.

Why the split is the point

This isn't kilter begrudgingly supporting ArgoCD. The two-tier split is the correct architecture, and it's the thing hosted PaaS platforms structurally cannot offer.

Kilter, by feeding GitOps instead of replacing it, gets both. The middle tier keeps the operator and its velocity; prod keeps ArgoCD and its trust. The seam that was a leak when one tier tried to do both jobs becomes a boundary — the single place where imperative provisioning is transmuted into declarative declaration, exactly at the Dev/QA → Prod edge where you want a gate anyway.

And because kilter produces standard artifacts — OCI image, OCI chart, committed manifests — your ArgoCD process doesn't need to know kilter exists. AppProjects, RBAC, sync windows, the PR template, the audit trail: all yours, unchanged. Kilter is an upstream source of reviewed bundles, not a competing authority.

What "seamless" actually means

It means the app code and the app image never know which tier reconciled them. The same container that ran in the kilter middle environment, against the same chart, with the same environment shape, runs in prod — the only difference is that prod's Namespace, identity, and credentials were committed by a human-reviewed PR and reconciled by ArgoCD, while the middle tier's were provisioned by an operator and reconciled by Flux.

A promotion is a PR. You see, in the diff, exactly what gets materialized: the Namespace, the ServiceAccounts, the SOPS secrets, the Crossplane claims. You review it, you merge it, ArgoCD syncs it. Nothing is hidden, because there's no operator in the prod path to hide anything.

Roadmap — late Q3

  • kilter package — freeze a middle-tier env and emit the self-sufficient prod bundle (Namespace, SAs, HelmRelease or rendered manifests, ArgoCD Application / AppProject).
  • Declarative provisioners — Crossplane claim templates and a CNPG Database mapping, so the database/user, bucket, and (where a CRD exists) workflow namespace become ArgoCD-reconciled objects instead of operator calls.
  • SOPS-commit path — stateless connection secrets committed, not injected, on promotion.
  • Promotion diffkilter package --dry-run showing exactly what will be materialized, so the prod PR is reviewable before it exists.
  • ArgoCD-native output — first-class Application / AppProject generation, so kilter writes into an ArgoCD repo as naturally as it writes into a Flux one today.

The one-line version

GitOps is the control plane. Kilter is the fastest way to author for it. In late Q3, a kilter app in your Dev/QA middle tier promotes to pure-Kubernetes prod through your ArgoCD process — same image, same chart, same config, declared not injected, reviewed not assumed. Your production engineering process stays yours; kilter just feeds it the best bundles in the business.

Where to go next

  1. Read modern Kubernetes is delightfully elegant and almost even small — the seam this promotion closes.
  2. See what makes a platform agent-native — why declarative + reconciled is the right control style.
  3. Read why IDPs are the wrong abstraction — another flavor of "don't try to be the control plane."
  4. Start your free trial — the full platform, 7 days, no card.