← All churts

Replikate — ConfigMap and Secret Replication Across Kubernetes Namespaces

Replikate — ConfigMap and Secret Replication Across Kubernetes Namespaces

The Gap Everyone Hits Eventually

If you run anything non-trivial on Kubernetes, you’ve hit this wall: a ConfigMap or Secret is namespace-scoped, and there’s no built-in way to share one across namespaces. A registry pull secret, a CA bundle, a shared config block, a wildcard TLS cert — the moment you have more than a couple of namespaces, you need the same object in all of them, and Kubernetes gives you nothing.

So people paper over it. A kubectl apply loop in CI. A Helm chart that stamps the secret into every release. A hand-maintained list of namespaces that’s wrong the day someone adds a new one. All of these share the same failure mode: they’re push-once solutions to a keep-in-sync problem. The source changes and the copies rot. A new namespace appears and nobody remembers to seed it.

I got tired of writing the loop. So I wrote Replikate.

What It Does

Replikate is a lightweight Kubernetes controller that replicates ConfigMaps and Secrets across namespaces, driven entirely by an annotation on the source object. You annotate the thing you want shared, and Replikate maintains a managed copy of it everywhere you asked for:

apiVersion: v1
kind: ConfigMap
metadata:
  name: shared-config
  namespace: default
  annotations:
    replikate.brainchurts.com/sync: "team=web"   # "" for all namespaces
data:
  LOG_LEVEL: "info"

The annotation value is the whole interface:

  • A label selector (team=web) → copies land in every namespace whose labels match.
  • An empty string ("") → copies land in every namespace, cluster-wide.

And then it actually keeps them in sync, which is the part the kubectl loops never do:

  • Source changed → every copy is updated to match.
  • Source deleted, or the annotation removed → the copies are cleaned up, via a finalizer, so nothing is left orphaned.
  • A new namespace appears that matches the selector → it gets populated automatically, no human in the loop.

That last one is the whole point. Replikate turns “share this everywhere” from a thing you do into a thing that’s simply true and stays true.

The Design Decisions That Matter

A controller that writes objects into every namespace in your cluster is a controller that can do a lot of damage if it’s careless. Two decisions define the whole safety posture:

It never touches an object it doesn’t own. Every copy Replikate creates is stamped with replikate.brainchurts.com/managed-by=replikate plus origin labels pointing back at the source. Before it writes, it checks that stamp. If a ConfigMap named shared-config already exists in a target namespace and you made it by hand, Replikate leaves it alone — it will not clobber an object it doesn’t manage. The blast radius is strictly the set of objects it created itself.

Cleanup is a finalizer, not a hope. When you delete the source or pull the annotation, the copies have to go too — otherwise you’ve traded a sync problem for a garbage problem. Replikate uses a Kubernetes finalizer on the source so deletion blocks until the managed copies are actually removed. Cleanup is part of the delete, not a background cron that might run.

How It’s Built

Replikate is written in Go on top of sigs.k8s.io/controller-runtime (the same foundation Kubernetes’ own operators are built on), targeting Kubernetes 1.31. The shape is deliberately small: a shared syncer holds the reconciliation logic — resolve the selector to a namespace set, diff desired copies against actual, create/update/prune — and two thin controllers (one for ConfigMaps, one for Secrets) feed it. The core is a few hundred lines. A controller you can read in one sitting is a controller you can trust in production.

The operational packaging is intentionally boring, which is the right kind of boring for something that touches every namespace:

  • Ships as a distroless/static:nonroot container — no shell, no package manager, minimal attack surface.
  • Installs via a Helm chart that carries the Deployment, ServiceAccount, and a tightly-scoped ClusterRole/RoleBinding.
  • CI (GitHub Actions) builds and pushes the image on merge to main.

Honest About the Edges

It’s an MVP, and I’d rather name the limits than pretend they aren’t there:

  • It replicates ConfigMaps and Secrets intra-cluster only. Cross-cluster sync is a planned follow-up, not a shipped feature.
  • If you hand-edit a managed replica, it’s corrected on the next source change or the controller’s periodic resync — not instantly.
  • Two sources with the same name in different namespaces that both target a third namespace will collide on name. Give such sources distinct names.

None of these are hard blockers for the job it’s built for — they’re the honest scope of a v0.

Get It

Replikate is BSD 3-Clause licensed and on GitHub at github.com/cwolsen7905/replikate. Annotate a source, helm install, and stop writing the kubectl loop.

It’s a small tool that does one irritating thing well — which is exactly the kind of tool I most enjoy having around.

← All churts