GitOps Reverser is a Kubernetes operator that turns Kubernetes API resources into clean YAML in Git. It is configurable, and can be used as:
- a live audit trail, or
- a "reverse" GitOps-reconcilable repo that keeps API-first workflows intact.
The broader pattern is described at reversegitops.dev.
Want proof? See this example commit in ConfigButler/example-audit.
- Reconciles existing Kubernetes API state into Git: the repo reflects the exact current state.
- Captures live changes through watches.
- Includes real actors for every change if you configure kube-apiserver audit webhooks.
It edits the intent layer: the documents a human authored. The expansion layer a controller
derives from them stays out of scope. So it cannot reverse Helm-rendered resources into a clean
values.yaml, and it will not invent structure for templating it does not model. Simple Kustomize
layouts are supported (see below); the full verdict table is in
support-contract.md.
Good fit if you want API-first and Git at the same time.
| Good fit | Poor fit |
|---|---|
| Clusters where you can grant watch/RBAC and write to Git (optionally run Valkey/Redis for the full feature set) | Production HA requirements today |
| Teams that want API-to-Git capture first, then named author attribution later | Shared paths with two always-on writers fighting over the same resources |
| API-first or hybrid teams that still want Git history; brownfield discovery, hotfix capture, migration toward GitOps | Teams who want Git to stay the write path, with humans editing manifests first |
- Watches the Kubernetes API for the resource types each
GitTargetclaims. Watch is the single source of object state. - Sanitizes each change (status,
managedFields, and runtime noise removed) and diffs it against the current Git content. - Writes stable YAML to Git with useful commit metadata, authored according to the mode below.
It can also:
- Encrypt
Secretvalues before commit with SOPS + age (Secret-shaped custom resources can opt in). - SSH-sign every commit via
GitProvider.spec.commit.signing. - Group changes within a time window into a single commit.
- Take your own commit message and "why" from a
CommitRequest.
Every commit carries a Git author and a Git committer. By default both are one configured
identity (configured-author). Turn on attribution and the author becomes the real Kubernetes
actor (user, service account, or CI identity), while the committer never moves. That needs
kube-apiserver audit delivery (managed control planes like EKS/GKE/AKS generally do not expose it)
plus Valkey/Redis: see the attribution setup guide.
If attribution is enabled but a live change has no usable audit fact, the commit says so: its author
is unknown (attribution unresolved), and the configured committer identity is never substituted in
its place. For a change that should have a named actor, treat that identity as a signal to verify the
audit policy, webhook route, source identity, and Redis connectivity.
Valkey/Redis is optional but advised. Without it the default mode works fine; adding it unlocks
warm-restart cursors, CommitRequest author capture, and attribution.
While the watch is connected the operator sees each individual update and commits it, so Git tracks
changes as they happen. Across a gap (pod restart, disconnect, 410 Gone) it reconciles to current
state instead of replaying versions it never saw, so edits made during the gap collapse into one
commit. Nothing is lost or left stale; deletes are reconciled on reconnect. See
docs/architecture.md for replay and 410 Gone details.
The write path runs kustomize itself (sigs.k8s.io/kustomize/api) in memory, with no plugins, no
exec, no network, and no remote bases. That render decides where a change belongs and checks the
result before committing. What it can do:
- Edit
resources:(andbases:),namespace:,images:, andreplicas:as real declarations. - Write a change where the value lives in source (the document itself, or the governing
images:/replicas:entry) instead of mirroring rendered output over your source files. - Add a new file to the right
resources:list in the same commit, and remove the entry when that file's last document is deleted, so the repo never stops building. - Support
base/+overlays/{env}/: the base is read-only context, never written through an overlay. - Create a missing
images:/replicas:entry in an overlay, so one environment changes without touching a shared base, and author a$patch: deletefor an object an overlay inherits. - Read
patches:(local strategic-merge files),commonLabels,labels, andcommonAnnotationsas read-only build context. - Verify every commit by re-rendering before and after, refusing it unless your change lands exactly and nothing else moves.
It refuses these by name, before writing anything, and reports Stalled=True: generators,
components, namePrefix/nameSuffix, replacements, vars, helmCharts, plugins, inline and
JSON6902 patches, remote bases, and any field it does not model. One known gap: a strategic-merge
patch that edits a field of a base-owned object is not authored yet. Reasoning in
kustomize-support-boundary.md.
Early-stage software; CRDs and behavior may still change.
- Runs as a single controller pod (
replicas=1). - Shared-resource bi-directional workflows need explicit coordination.
- Source recovery covers Kubernetes manifests and simple Kustomize layouts, not Helm authoring models.
- Tested against Kubernetes
1.36; other versions may work but are not in the matrix. - Runtime behavior is deterministic: no AI or heuristic mutation at runtime.
Good fit for pilots, lab clusters, brownfield discovery, and design partners who can tolerate change. Production use should follow an environment-specific review.
Roughly in priority order:
- High availability:
replicaCount > 1is rejected today. It needs leader/ownership coordination so two replicas never write the sameGitTarget, and Redis becomes required rather than advised. - A stabilized configuration surface: all six CRDs are
v1alpha3with no conversion path yet. - More documentation: day-2 operations, troubleshooting, and worked examples per layout.
- A durable worker queue: a crash between advancing the watch cursor and landing the write can currently skip work on restart.
- Write-collision safety across
GitProviderobjects sharing a repository. Until then, keep oneGitProviderper repository. - Better queue and worker observability: enough metrics to run it without reading logs.
- More control over output layout, plus filtering cluster-generated noise out of the Git view.
Backlog in docs/TODO.md; longer-range directions in docs/future/.
This brings up the demo: a starter GitProvider, GitTarget, and WatchRule in a
gitops-reverser-quickstart-demo namespace, watching ConfigMaps there and writing them to
<your-repo>/live-cluster on main. It runs in configured-author mode (no Redis) by default. The
chart also renders the cluster-scoped default ClusterProvider the starter target resolves against.
Prerequisites: a Kubernetes cluster with kubectl, Helm 3, and cert-manager for TLS.
The controller mounts an admission certificate at startup, so cert-manager must be healthy before
you install the chart. (--set servers.admission.enabled=false drops the dependency; the
chart README covers bring-your-own certificates.)
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.21.0/cert-manager.yaml
kubectl wait --for=condition=ready pod -l app.kubernetes.io/instance=cert-manager -n cert-manager --timeout=300sCreate an (empty) repository the operator will write to, then generate a key. Add the public half as a deploy key with write access; the private half becomes the Secret in the next step:
ssh-keygen -t ed25519 -C "gitops-reverser@cluster" -f /tmp/gitops-reverser-key -N ""
# Add /tmp/gitops-reverser-key.pub to your Git provider as a deploy key (write access)Do this before installing. The starter GitProvider is only re-checked about every 5 minutes, so
if the Secret is missing at install time your first commit can be minutes late; having it ready up
front lets the starter resources go Ready on the first reconcile:
kubectl create namespace gitops-reverser-quickstart-demo \
--dry-run=client -o yaml | kubectl apply -f -
kubectl create secret generic git-creds \
--namespace gitops-reverser-quickstart-demo \
--from-file=ssh-privatekey=/tmp/gitops-reverser-key \
--from-literal=known_hosts="$(ssh-keyscan github.com 2>/dev/null)" \
--dry-run=client -o yaml | kubectl apply -f -SSH host-key verification fails closed, so the known_hosts line is required. Existing Flux or Argo CD
credentials Secrets are accepted as-is (they must have write access). See
docs/configuration.md for accepted Secret shapes and
docs/github-setup-guide.md for the full GitHub guide and HTTPS/PAT
fallback.
Point the starter GitProvider at your repo and install:
helm install gitops-reverser \
oci://ghcr.io/configbutler/charts/gitops-reverser \
--namespace gitops-reverser \
--create-namespace \
--set quickstart.enabled=true \
--set-string quickstart.gitProvider.url=git@github.com:OWNER/REPO.gitReplace OWNER/REPO with your repository (angle brackets would be parsed by the shell).
Three things worth knowing about this install:
- No Redis, so warm-restart cursors and author capture stay inactive. Add one with
--set queue.redis.addr=HOST:PORT(plusqueue.redis.auth.existingSecretif it needs auth). - Cluster-wide read on every watchable type, including Secrets (
rbac.watchTypes.mode=any): fine for a demo, probably too wide for a real cluster.docs/rbac.mdnarrows it. - SOPS is on for the starter target, so a
sops-age-keySecret is generated in the demo namespace with a backup reminder. Only matters once you mirror Secrets; back it up if you keep the demo.
Wait for the controller. On a fresh install this waits on cert-manager issuing the certificate:
kubectl rollout status deployment/gitops-reverser -n gitops-reverser --timeout=300sThen check the starter resources:
kubectl get gitprovider,gittarget,watchrule -n gitops-reverser-quickstart-demoThe GitProvider and WatchRule report Ready=True; the GitTarget reports Validated=True.
Its aggregate Ready stays Unknown until first source discovery, which is the expected state.
kubectl create configmap test-config --from-literal=key=value -n gitops-reverser-quickstart-demoA new commit should land in your repository within seconds. If none appears:
kubectl logs -n gitops-reverser deploy/gitops-reverser
kubectl describe gitprovider,gittarget,watchrule -n gitops-reverser-quickstart-demoTwo GitTarget conditions stop the data plane and are worth recognizing: ClusterProviderNotFound
(the default ClusterProvider is missing) and NamespaceNotAuthorized (its allowedNamespaces
selector does not cover the demo namespace).
To tear the demo down: helm uninstall gitops-reverser -n gitops-reverser and
kubectl delete namespace gitops-reverser-quickstart-demo.
Note: the
defaultClusterProvideris cluster-scoped and chart-owned, so uninstalling takes it with it, holding every otherGitTargetin the cluster unready. Mind that if you run the demo alongside a real deployment.
Turn on attributed-author mode so the real Kubernetes actor becomes the Git author. Needs audit
delivery and Valkey/Redis: the attribution setup guide walks it
through.
ConfigButler can run a small, secure, public-facing Kubernetes API for you: we operate GitOps Reverser and authorize your end users, with forward-deployed engineers to get you started. You keep a clean, self-owned Git repo where your users express their intent.
Start with the stable docs surface:
docs/README.mddocs/configuration.mddocs/attribution-setup-guide.mddocs/security-model.mddocs/rbac.md: the two ClusterRoles, and how to stop the reverser enumerating Secretsdocs/commit-signing.mddocs/github-setup-guide.mddocs/sops-age-guide.mddocs/bi-directional.mddocs/alternatives.md: nearby tools and when another approach fits better
If this workflow matches a real problem, feedback is very welcome. The most useful reports are install attempts, first-commit experience, audit delivery issues, Git output shape, CRD ergonomics, and security or operational concerns.
- Read the Reverse GitOps manifesto for the broader pattern.
- Connect on LinkedIn. Feedback, questions, and ideas are welcome.
Issues, docs fixes, and code contributions are welcome. See CONTRIBUTING.md.
Apache 2.0
