# Wave restarts Pods when the ConfigMap behind them changes

> Wave is a Kubernetes controller that watches Deployments, StatefulSets and DaemonSets together with the ConfigMaps and Secrets they mount, and triggers a rolling update when the mounted configuration changes.

**wave-k8s/wave** — Kubernetes configuration tracking controller

- Repository: https://github.com/wave-k8s/wave
- Stars: 777 · Forks: 83
- Language: Go
- License: Apache-2.0
- Published: 2026-10-20 · Updated: 2026-10-20 · Language: en
- Canonical page: https://hysenlabs.com/projects/wave-k8s-wave

## The problem is configuration that is deployed but never applied

Wave watches Deployments, StatefulSets and DaemonSets within a Kubernetes cluster and ensures that their Pods always have up to date configuration. The mechanism is narrow and specific: by monitoring mounted ConfigMaps and Secrets, Wave can trigger a Rolling Update of the Deployment when the mounted configuration is changed.

The problem it solves is described in the README's introduction. Some applications can automatically reload their configuration and continue running with the updated configuration, while others will just continue running the old configuration until their process dies. Wave is here to help those applications that do not support dynamic configuration reloading.

The situation it creates is common. Configuration is deployed using ConfigMaps and Secrets, mounted into the Pod either as environment variables or as files. When that configuration is updated and the consuming software cannot reload dynamically, it keeps running the old configuration until the Pod is killed. The README calls the result an inconsistency between the supposed desired state of the application and the running state of the application.

Wave closes that gap. It monitors Deployments and their underlying configuration and triggers the Kubernetes Deployment controller to update the application and bring up new Pods whenever the underlying configuration changes. So when a ConfigMap or Secret is updated, all Deployments mounting that configuration bring up new Pods and remove the old Pods with the out-of-date configuration.

The stated payoff is that developers can discover misconfiguration as it is deployed, rather than when the Pods happen to be re-cycled. That is the real cost being addressed: a configuration change that silently does nothing until a node failure or a deployment three weeks later.

## The Helm chart is where the production settings live

Wave is released periodically, and the list of changes is in the CHANGELOG file. Public Docker images for releases since v0.4.0 are on Quay at quay.io/wave-k8s/wave, and Helm charts live in the charts directory of the repository and are hosted through GitHub Pages.

The install is two commands, and the second one names the chart explicitly:

```
$ helm repo add wave-k8s https://wave-k8s.github.io/wave/
$ helm install wave wave-k8s/wave
```

Helm will install a minimal setup, which is fine for a trial and not what you want in production. The README recommends a specific set of values for production, and the reason for each one is readable from the comment next to it:

```yaml
# run two replias for HA
replicas: 2

# enable webhooks for faster updates
webhooks:
  enabled: true

# make sure that replicas do not restart at the same time
pdb:
  enabled: true

# schedule to multiple AZs
topologySpreadConstraints:
  - maxSkew: 1
    topologyKey: topology.kubernetes.io/zone
    whenUnsatisfiable: DoNotSchedule
    labelSelector:
      matchLabels:
        app: wave
```

The comments carry the intent, which is worth reading carefully rather than pasting blindly. Two replicas for high availability, a pod disruption budget so the replicas do not restart at the same time, and topology spread constraints across availability zones so the controller itself is not a single zone failure. The resource block that follows sets requests of 256Mi memory and 25m CPU and limits of 2Gi memory, with a comment telling you to adjust it to your setup.

The webhooks block is the one to think about. The README presents enabling webhooks as a choice for faster updates, not as a default, and that is a hint that it changes the shape of the controller's behaviour in the cluster rather than just making it faster.

## The controller needs read access to every Secret in the cluster

If you are using RBAC within your cluster, you must grant the service account used by your Wave instance permission to read all Secrets, ConfigMaps and Deployments and the ability to update Deployments within each namespace in the cluster. Example ClusterRole and ClusterRoleBindings are available in the config/rbac folder or as part of the helm chart.

That requirement is the security story of this project, and it deserves an explicit decision rather than a copy and paste. Wave works by watching Secrets, which means the controller's service account can read every Secret in the cluster it is granted access to. In a cluster where a Secret holds a database password, a payment key or a certificate, that is a broad grant for a small utility.

The alternative is that the grant is scoped to a namespace rather than the whole cluster, and the README's wording supports that reading, since it speaks of updating Deployments within each namespace in the cluster. Whether the watch can be narrowed further is not documented, so if your threat model requires a tighter boundary, that is a question for the maintainers rather than something to infer from the chart.

The rest of the permission set is more ordinary. Wave only edits Deployments, DaemonSets and StatefulSets, which is also the basis of its version compatibility story, and it never writes to a ConfigMap or a Secret. It reads configuration and rewrites workload templates, and the reconciliation is done through the standard controller machinery.

For a cluster that is already RBAC constrained, or a multi tenant platform where a namespace scoped controller matters, this permission set is the first thing to model.

## Leader election keeps two replicas from fighting over the same workload

Wave can be run in an active-standby HA configuration using Kubernetes leader election. When leader election is enabled, each Pod will attempt to become leader and, whichever is successful, will become the active or master controller. The master will perform all of the reconciliation of Resources. The standby Pods will sit and wait until the leader goes away, and then one standby will be promoted to master.

The mechanism is the standard Kubernetes pattern, and it is enabled with three flags:

```
--leader-election=true
--leader-election-id=<name-of-leader-election-configmap>
--leader-election-namespace=<namespace-controller-runs-in>
```

The name argument and the namespace argument both have to be supplied, and the namespace is where the leader election ConfigMap is created, which is a detail worth knowing when the controller runs in a namespace with a restricted policy.

This matters more for Wave than for many controllers, because its action is destructive in a mild but real way. A rolling update of a Deployment replaces Pods. If two controllers both reacted to the same ConfigMap change, you would get two rolling updates of the same Deployment, and a workload that was already restarting would restart again. The active-standby model means exactly one controller acts on a change, and the second replica is there purely to take over if the first dies.

The consequence for the chart is that the production values run two replicas, and leader election is what makes that safe. A single replica deployment is simpler, and the failure mode is that the controller is unavailable until Kubernetes reschedules it.

## The sync period is ten hours by default, and that is deliberate

The controller uses Kubernetes informers to cache resources and reduce load on the Kubernetes API server. Every informer has a sync period, after which it will refresh all resources within its cache, and at that point every item in the cache is queued for reconciliation by the controller.

By default, sync will happen every 10h. Kubernetes will inform Wave about changes in any Deployment, DaemonSet, StatefulSet, Secret or ConfigMap in the meantime and Wave will trigger a reconciliation right away.

So the design is event driven with a slow safety net. The 10 hour resync is not the latency of your configuration rollout, it is a periodic full re-check that catches anything the watch stream missed. Reducing it is offered as a debugging tool, with an explicit flag:

```
--sync-period=5m // Default value of 10h (10 hours)
```

The README is honest about the cost of that change. It will ensure that every resource will be reconciled at least every 5 minutes. Please note that this will cause more load on your API and increase CPU load in the Wave container. It might also increase latency during the time of the sync.

That last sentence is the part to remember. Reconciliation is not a no-op, it queues every cached item, so a short sync period is not a safety net but a workload. If you are debugging a missed update, the flag is the right tool for a day. If you are tempted to lower it permanently to make updates feel instant, the informer events already deliver updates immediately, so you would be buying nothing and paying on every cluster.

## Kubernetes version support moves with the client library

Wave uses the golang Kubernetes client library, which only supports the previous and the next Kubernetes version. However, since Wave only edits Deployments, Daemonsets and StatefulSet, the project can support older Kubernetes versions as long as no fields were removed from those three objects.

The compatibility table in the README tracks this over time. Wave 0.5 used API client 1.14 with a maximum supported Kubernetes version of 1.15 and no end-to-end tested versions listed. Wave 0.6 through 0.9 used API client 1.29 with a maximum of 1.30 and 1.21 and 1.30 end-to-end tested. Wave 0.10 moved to API client 1.31 with a maximum of 1.32 and 1.21 and 1.31 tested. Wave 0.11 uses API client 1.34 with a maximum of 1.34 and 1.28 and 1.34 end-to-end tested.

The current release tags show the same pattern. wave-4.6.0 and v0.12.0 were both published on 2026-09-09, and v0.11.0 on 2026-03-03. The repository is not archived and the last push was on 2026-09-09.

That double naming is worth noticing. There is a chart oriented tag, wave-4.6.0, and a version tag, v0.12.0, published on the same day, so the chart version and the application version do not obviously track each other. Anyone automating an upgrade needs to look at the CHANGELOG to see which application version a chart actually deploys, and the README points there for the list of changes.

The go.mod in the repository corroborates the current line: the module targets go 1.24.0 with toolchain go1.24.1, and it depends on k8s.io/api, apimachinery, client-go and code-generator at v0.34.1, with sigs.k8s.io/controller-runtime at v0.21.0. The client library version and the supported Kubernetes version are therefore the same number, which is exactly the constraint the README describes.

## Kustomize, the controller runtime and a permissive licence

Wave is a Kubebuilder based project, and as such it has auto-generated Kustomize configuration as an example of how to install the controller in the config folder. The image reference in that configuration is quay.io/wave-k8s/wave, and the repository carries a charts directory, a cmd directory, a pkg directory, a test directory, a hack directory and a config directory, with a PROJECT file at the root that records the Kubebuilder scaffolding metadata.

The build story is a standard controller-runtime project with a Kubebuilder twist. The Makefile wires up controller-gen at v0.19.0, an envtest binary pinned to Kubernetes 1.34.0, and golangci-lint at v1.64.8, with targets for generate, verify, fmt, vet and lint. The verify target is the interesting one: it runs the generator and then fails if the git status is not clean, which is how generated code is kept in sync with the source. The tests use Ginkgo and Gomega, and the Dockerfile builds the manager binary with a version stamp injected through ldflags, then copies it into an alpine:3.22 image whose entrypoint is /bin/wave.

Licensing is Apache-2.0, with the LICENSE file at the root of the repository and the badge in the README. For a controller you run inside your own cluster, that is the least complicated part of the decision.

What is worth thinking about is the operational shape rather than the licence. Wave is a controller with cluster-wide read access to Secrets and write access to workload templates, and it acts by replacing Pods. A team that adopts it should decide explicitly whether the trigger for a rolling update is acceptable for the workloads in scope, since a ConfigMap that is edited for a debugging reason will restart the Pods that mount it.

## Conclusion

Wave suits a cluster where applications read their configuration at startup and nobody wants to wait for a Pod to be recycled before a changed ConfigMap takes effect, because the controller removes the gap between the configuration you declared and the configuration the process is running. It is the wrong tool for an application that already reloads its configuration dynamically, and it is the wrong tool if you cannot grant a controller permission to read every Secret in the cluster, because that permission is the price of the feature. Before adopting, check the compatibility table for your Kubernetes version, since the client library only supports the previous and the next release, and decide deliberately whether to enable webhooks, since the README presents them as an option for faster updates rather than a default.

## FAQ

### What does the Wave Kubernetes controller actually do?

Wave watches Deployments, StatefulSets and DaemonSets along with the ConfigMaps and Secrets they mount, and triggers a Rolling Update of the Deployment when the mounted configuration changes, so Pods are replaced with ones that see the new configuration.

### How do I install Wave with Helm?

Add the chart repository and install the chart with helm repo add wave-k8s https://wave-k8s.github.io/wave/ followed by helm install wave wave-k8s/wave. That installs a minimal setup; the README lists production values for two replicas, webhooks, a pod disruption budget and topology spread constraints.

### What RBAC permissions does Wave need?

The service account used by the Wave instance must be able to read all Secrets, ConfigMaps and Deployments and to update Deployments within each namespace in the cluster. Example ClusterRole and ClusterRoleBinding files are in the config/rbac folder and in the helm chart.

### Which Kubernetes versions does Wave support?

Wave uses the golang Kubernetes client library, which only supports the previous and the next Kubernetes version, though older versions work as long as no fields were removed from Deployments, DaemonSets and StatefulSets. The README table shows Wave 0.11 with API client 1.34, a maximum supported version of 1.34 and 1.28 and 1.34 end-to-end tested.

### What is the default sync period in Wave and when should I change it?

The default sync period is 10h, used by the informers to refresh their cache and requeue every resource for reconciliation, while watch events trigger reconciliation immediately. The README presents --sync-period=5m as a debugging step and warns that it causes more API load, more CPU load in the container and extra latency during the sync.

## Sources

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

---

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