# kubernetes-replicator copies a secret into every namespace that asks for it

> The Mittwald kubernetes-replicator is a Go controller that keeps secrets, config maps, roles, role bindings and service accounts in sync across Kubernetes namespaces, driven by annotations on the source object rather than by a central configuration file. The two mechanisms are push-based, where the source names its targets, and pull-based, where the target names its source, and the controller also has to be granted enough privilege to create roles in namespaces where it has not been given any.

**mittwald/kubernetes-replicator** — Kubernetes controller for synchronizing secrets & config maps across namespaces

- Repository: https://github.com/mittwald/kubernetes-replicator
- Stars: 1,121 · Forks: 127
- Language: Go
- License: Apache-2.0
- Published: 2026-10-19 · Updated: 2026-10-19 · Language: en
- Canonical page: https://hysenlabs.com/projects/mittwald-kubernetes-replicator

## One secret, many namespaces, and who decides

The problem is a Kubernetes cluster with the same credential in several namespaces and a person copying it by hand. The repository contains a custom Kubernetes controller that makes secrets and config maps available in multiple namespaces, and the objects it replicates are broader than that, since the title covers ConfigMap, Secret and Role, RoleBinding and ServiceAccount replication. The design decision that makes it work is that the configuration lives on the objects themselves. There is no central file listing which secret goes where, and no controller config to keep in step with reality, and instead each object carries annotations that say what should happen to it. The two directions are the heart of it, push-based replication, where the source pushes out to named targets, and pull-based replication, where the target pulls from a source it names. That choice has real consequences for who owns a copy, and the documentation treats it as the first thing to decide.

## Push-based: the source names its targets

Push-based replication pushes secrets, config maps, roles and role bindings into namespaces when new namespaces are created, or when the source object changes, and there are two ways to say where. The first is name-based. You add a replicator.v1.mittwald.de/replicate-to annotation to your secret, role, role binding or config map, and the value is a comma separated list of permitted namespaces or regular expressions, so namespace-1,my-ns-2,app-ns-[0-9]* replicates only into namespace-1 and my-ns-2 and into any namespace matching the regular expression:

```yaml
apiVersion: v1
kind: Secret
metadata:
  name: test-secret
  annotations:
    replicator.v1.mittwald.de/replicate-to: "my-ns-1,namespace-[0-9]*"
data:
  key1: <value>
```

The second is label-based, using replicator.v1.mittwald.de/replicate-to-matching with an arbitrary label selector as the value, so a namespace qualifies by carrying the labels rather than by matching a name:

```yaml
apiVersion: v1
kind: Secret
metadata:
  name: test-secret
  annotations:
    replicator.v1.mittwald.de/replicate-to-matching: >
      my-label=value,my-other-label,my-other-label notin (foo,bar)
data:
  key1: <value>
```

The label-based mode has a behaviour worth knowing, because when the labels of a namespace change, any resources that were replicated into it by label and no longer qualify are deleted, and afterwards the resources that do match the new labels are replicated in. Both methods can be used on the same object by specifying both annotations.

## Pull-based: the target names its source

Pull-based replication inverts the ownership. Instead of the source naming its targets, the source says it may be replicated and to where, and each target says which source it wants. The source carries two annotations. The first, replicator.v1.mittwald.de/replication-allowed, is set to true to indicate that the object can be replicated, and the second, replicator.v1.mittwald.de/replication-allowed-namespaces, holds a comma separated list of permitted namespaces or regular expressions, the same expression syntax as the push side:

```yaml
apiVersion: v1
kind: Secret
metadata:
  name: test-secret
  annotations:
    replicator.v1.mittwald.de/replication-allowed: "true"
    replicator.v1.mittwald.de/replication-allowed-namespaces: "my-ns-1,namespace-[0-9]*"
data:
  key1: <value>
```

The target is then an empty secret or config map with a replicator.v1.mittwald.de/replicate-from annotation whose value names another secret or config map in the namespace and name notation. The empty target is the point, because it is a declarative statement of intent rather than a copy that has to be kept in step, and the controller fills it. The pull model is the one to reach for when the namespaces are created by someone else, because a team that owns a namespace can ask for a secret without the team that owns the secret having to know the namespace exists.

## Replicating roles means the controller needs real privilege

Replicating Roles and RoleBindings is the part of this controller with a security cost, and the documentation is unusually clear about it. To create a new role, your own account needs to have at least the same set of privileges as the role you are trying to create, and the chart offers two ways to grant those permissions to the service account the replicator uses. The first is the value grantClusterAdmin set to true, which grants the service account admin privileges, and it is false by default because a service account with that level of access might be undesirable due to the potential security risks attached. The second is to set the lists of needed API groups and resources explicitly through the privileges value, which is a list of pairs of API group and resource lists:

```yaml
serviceAccount:
  create: true
  annotations: {}
  name:
  privileges:
    - apiGroups: [ "", "apps", "extensions" ]
      resources: ["secrets", "configmaps", "roles", "rolebindings",
      "cronjobs", "deployments", "events", "ingresses", "jobs", "pods", "pods/attach", "pods/exec", "pods/log", "pods/portforward", "services"]
```

The trade is explicit. The service account needs at least the same privileges as any role it is asked to create, so the choice is between one very powerful service account and a list of specific API groups and resources that says exactly what it may touch.

## Installing it, from a chart or from two YAML files

There are two deployment paths, and the manual one is short enough to be worth knowing. The Helm path adds the Mittwald repository, updates it, and installs the chart:

```bash
helm repo add mittwald https://helm.mittwald.de
helm repo update
helm upgrade --install kubernetes-replicator mittwald/kubernetes-replicator
```

The manual path applies two files from the deploy/ directory in the repository, the RBAC first and then the deployment itself:

```bash
kubectl apply -f https://raw.githubusercontent.com/mittwald/kubernetes-replicator/master/deploy/rbac.yaml
kubectl apply -f https://raw.githubusercontent.com/mittwald/kubernetes-replicator/master/deploy/deployment.yaml
```

The ordering matters and the comments in the documentation say so, since the roles and service accounts have to exist before the controller that uses them starts. The README also covers a special case for TLS secrets, which is worth reading before you replicate one, since a TLS secret has a type and a key structure that a plain secret does not. The repository ships a Dockerfile, a Dockerfile.buildx, a .goreleaser.yaml for the release build, a SECURITY.md, and the source is organised into main.go, config.go, a replicate/ package, a liveness/ package, a test/ directory and a deploy/ directory.

## A small Go binary and a scratch image

The implementation is deliberately plain, and the Dockerfile shows the shape of the deployment. The image is built FROM scratch, which means no operating system underneath the binary, and the entrypoint is the replicator itself:

```dockerfile
FROM        scratch
LABEL       MAINTAINER="Martin Helmich <m.helmich@mittwald.de>"
COPY        kubernetes-replicator /replicator
ENTRYPOINT  ["/replicator"]
```

A scratch image is the right choice for a controller whose entire job is to talk to the Kubernetes API, since there is nothing in it for an attacker to use, and the image is small enough to pull quickly on every node that schedules the controller. The dependencies are the ones you would expect for a Kubernetes controller, with k8s.io/api, k8s.io/apimachinery and k8s.io/client-go at v0.33.3, logrus for logging, hashicorp/go-multierror for aggregating errors, pkg/errors for wrapping, and testify for the tests. The module requires Go 1.24.13, and the releases move on a patch cadence, with v2.12.2 on 2025-11-24, v2.12.3 on 2026-02-12 and v2.12.4 on 2026-07-07.

## Where a secret replicator is the wrong tool

Two objections, and they are about the model rather than the implementation. The first is that replication creates copies. Every namespace that holds a copy is a place where the secret can be read, where it can be captured by an audit log, and where it has to be rotated, and a controller that keeps copies in step is doing you a favour only if the alternative, which is a secret in every namespace that a human copied once, is not already the pattern you have. If your platform already has a way to inject secrets at deploy time, a sealed secret store, or an external secret operator pulling from a vault, that is a better answer than copying, and the documentation does not claim otherwise. The second is the blast radius of the matching. A regular expression in the replicate-to annotation, or a label selector, decides which namespaces receive a credential, and a selector that is broader than intended will not fail loudly. The pull-based model reduces that risk, because a target namespace has to ask, but it does not remove it, because the target has to be trusted to ask. The controller is well built and small and does exactly one thing, and the judgement about whether that one thing is the right thing for your cluster is the judgement this repository cannot make for you.

## Conclusion

kubernetes-replicator is the right tool when several namespaces need the same secret and you want the copy to be created and removed automatically as namespaces come and go, because the pull-based model puts the decision on the target side and the controller handles the lifecycle. It is the wrong tool if you can inject secrets at deploy time instead, because every additional copy is an additional copy to rotate and to protect, and it is a poor fit for a cluster where secrets should come from an external store rather than from another namespace. Before adopting it, decide which direction you want, push or pull, because the two behave differently when a namespace is deleted, and read the label-based section, since a namespace whose labels change will have its replicated resources deleted and recreated, which is a visible event in someone else's namespace.

## FAQ

### What does the mittwald kubernetes-replicator copy between namespaces?

Secrets, config maps, and also Roles, RoleBindings and ServiceAccounts. It is a custom Kubernetes controller that makes secrets and config maps available in multiple namespaces, driven by annotations on the objects themselves.

### What is the difference between push-based and pull-based replication?

Push-based replication pushes the source out into namespaces when new namespaces are created or when the source changes, and the source names its targets by namespace name, regular expression or label selector. Pull-based replication makes each target name its own source with the replicator.v1.mittwald.de/replicate-from annotation, while the source only permits replication with the replication-allowed and replication-allowed-namespaces annotations.

### How do I install the replicator?

With Helm, by adding the mittwald repository with helm repo add mittwald https://helm.mittwald.de and running helm upgrade --install kubernetes-replicator mittwald/kubernetes-replicator, or manually by applying the RBAC file and then the deployment file from the deploy/ directory with kubectl apply -f, in that order.

### What permissions does the replicator service account need?

To create a new role, your own account needs at least the same set of privileges as the role being created. The chart can either grant cluster admin with grantClusterAdmin, which is false by default because of the security risks, or grant explicit API groups and resources through the privileges value.

## Sources

- [Issues](https://github.com/mittwald/kubernetes-replicator/issues)
- [License: Apache-2.0](https://github.com/mittwald/kubernetes-replicator/blob/master/LICENSE)
- [mittwald/kubernetes-replicator on GitHub](https://github.com/mittwald/kubernetes-replicator)
- [README](https://github.com/mittwald/kubernetes-replicator/blob/master/README.md)
- [Releases](https://github.com/mittwald/kubernetes-replicator/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/mittwald-kubernetes-replicator
