Self-hosted service
squat/kilo avatar
squat/kilo

Kilo: a WireGuard mesh that spans Kubernetes clusters

Kilo is a multi-cloud network overlay built on WireGuard and designed for Kubernetes (k8s + wg = kg)

2,301 stars142 forksGoApache-2.0

At a glance

What is it?
A multi-cloud network overlay for Kubernetes that puts every node into a fully connected WireGuard mesh, with a kg agent, a kgctl tool for VPN peers, and two different answers about which CNI it layers on top of.
Who is it for?
Adopt Kilo when you have nodes in more than one location, need a fully connected encrypted layer 3 network between them, and want to keep whatever CNI you already run inside each location. Do not adopt it to replace a cluster CNI, since add-on mode delegates all pod networking to your existing provider and only Kilo connects the locations.
Can I use it commercially?
Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 40 days ago.
What is it written in?
Mainly Go, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What the kg agent actually does on each node

The design is one sentence long: Kilo uses WireGuard to create a mesh between the different nodes in a cluster. Everything else follows from that.

The agent is named kg and it runs on every node. Its job, per the README, is setting up the public and private keys for the VPN plus the rules needed to route packets between locations. The resulting pod network is fully connected even when the nodes sit in different networks or behind NAT, which is the property that makes multi-cloud clusters possible rather than merely reachable.

Topology is how Kilo decides which nodes should peer directly. By default it meshes logical locations, meaning data centers or cloud providers, rather than individual nodes. It reads the standard Kubernetes region node label for this, and when your cluster does not set it, an annotation on each node carries the location instead.

shell
for node in $(kubectl get nodes | grep -i gcp | awk '{print $1}'); do kubectl annotate node $node kilo.squat.ai/location="gcp"; done

Meshing per location rather than per node is a scale decision. A cluster with three regions ends up with three fully connected WireGuard peers rather than three hundred, and the topology is described as fully customizable if that grouping does not match your routing.

One node per location needs a routable address. The README says at least one node in each location must have an IP reachable from the other locations, and if the cloud does not put that address on the node's Ethernet device, an annotation forces the endpoint value. Getting this wrong produces a mesh where one region cannot initiate connections, which is the classic symptom of a firewall between the UDP port rather than a WireGuard problem.

Getting kg into the cluster, four different ways

Installation is two apply commands: custom resource definitions first, then a manifest tailored to how your cluster was bootstrapped.

shell
kubectl apply -f https://raw.githubusercontent.com/squat/kilo/main/manifests/crds.yaml
kubectl apply -f https://raw.githubusercontent.com/squat/kilo/main/manifests/kilo-kubeadm.yaml

The README ships four variants of that second line, for kubeadm, bootkube, Typhoon and k3s. The shape of the DaemonSet is identical across them, so if none of those names matches your setup, read the manifests directory and copy the closest one rather than starting from scratch.

There are prerequisites that have nothing to do with Kubernetes. The WireGuard kernel module must be loaded on every node, which in-tree means any kernel from Linux 5.6 onward, and older distributions need the package installed. The nodes in the mesh also need an open UDP port, and the default is 51820.

Nodes that cannot run the kernel module are not excluded. The README points at a userspace WireGuard implementation documented in the repository, which means the overlay still works without in-kernel WireGuard. Treat that as a different performance profile rather than an equal alternative, since the fast path and the fallback path are different implementations of the same protocol.

The build side is Nix-based. The Dockerfile in the repository starts from a Nix image, builds cross-compiled binaries through the flake targets, assembles CNI plugin binaries separately, and finally copies kg and kgctl into an Alpine base image.

Add-on mode and the two answers about Cilium support

Kilo can be a complete networking provider or an add-on. In add-on mode it contributes the inter-location network and the VPN while the cluster existing provider keeps doing CNI management and local networking. The README uses Flannel as the worked example: Kilo handles between locations, Flannel handles within them.

Here is the contradiction worth pausing on. The add-on section states that Kilo currently supports running on top of Flannel, and the README carries no other provider name. The 0.7.0 release notes for 2026-03-04 lead with a re-written Cilium compatibility layer as one of the productionization fixes in that release, which implies Cilium support exists and was recently reworked.

Both statements come from the project and they disagree about scope. Rather than picking one, the useful move is to check the manifests directory, which the README links as the place to find examples, and look for a manifest matching the CNI you run. If there is no manifest for your provider, assume Flannel is the tested path and treat anything else as unsupported until you have verified it in a test cluster. Version 0.6.0 from 2023 described Cilium compatibility as initial, and 0.7.0 rewrote that layer, so the situation has been moving.

The other half of add-on mode is multi-cluster services, which the README describes as services spanning different Kubernetes clusters. That is the feature that differentiates Kilo from a plain VPN mesh, and it is also the least documented part of the repository.

VPN peers with kgctl and a WireGuard interface

Nodes are not the only things that can join the mesh. Kilo lets peers outside the cluster connect, which the README frames as the way cluster applications reach external services and the way developers debug cluster resources securely.

A peer is a custom resource, applied like any other Kubernetes object.

yaml
apiVersion: kilo.squat.ai/v1alpha1
kind: Peer
metadata:
  name: squat
spec:
  allowedIPs:
  - 10.5.0.1/32
  publicKey: GY5aT1N9dTR/nJnT1N2f4ClZWVj0jOAld0r8ysWLyjg=
  persistentKeepalive: 10

The allowedIPs field is what scopes the peer to a single address, so a laptop gets access to one pod address rather than to the whole mesh. persistentKeepalive is the standard WireGuard setting for peers behind NAT.

The second half is on your machine, not in the cluster. kgctl renders the WireGuard configuration for a named peer, and you hand that to the local WireGuard tooling.

shell
kgctl showconf peer squat > peer.ini
sudo wg setconf wg0 peer.ini

The second command needs root, and it assumes an interface called wg0 already exists. Neither the README nor the repository tree documents a helper that creates that interface, so this is the step where you will be reading the WireGuard documentation rather than the Kilo documentation. Plan for that, and remember the peer public key in your resource is the same key your local interface must present.

Version drift inside the container image

The Dockerfile is short enough to read in full, and it contains two mismatches that will matter if you build your own image.

The first is the Alpine version. The final stage is built on alpine 3.23, and the same stage defines an argument defaulting to v3.20 and then overwrites the package repository list with that version's main and community mirrors.

dockerfile
FROM alpine:3.23
ARG TARGETOS
ARG TARGETARCH
ARG ALPINE_VERSION=v3.20

The result is a 3.23 userspace resolving packages from 3.20 repositories. Both facts are in the file. If you want to know what you actually have, run the image and read the release file rather than inferring from either line.

The second is the CNI plugin version. The module file requires the containernetworking plugins library at v1.1.1 and the CNI library at v1.3.0, while the Dockerfile sets a plugin version argument to v1.9.0 and downloads those binaries from the plugins project releases into the CNI binary directory.

dockerfile
ARG CNI_PLUGINS_VERSION=v1.9.0
RUN apk add --no-cache curl && \
    curl -Lo cni.tar.gz https://github.com/containernetworking/plugins/releases/download/$CNI_PLUGINS_VERSION/cni-plugins-linux-$TARGETARCH-$CNI_PLUGINS_VERSION.tgz && \
    tar -xf cni.tar.gz

So the library Kilo links against and the plugin binaries shipped beside it are from different major generations. In standalone mode Kilo installs those binaries as its CNI, which is exactly the case where the gap is visible: the runtime plugin does the work while the library parses the configuration.

One more detail in that file explains the firewall handling. It fetches an iptables wrapper installer from the kubernetes-sigs project at a pinned commit and runs it without the sanity check, which is how the image works on distributions where iptables is a compatibility shim over nftables. Note that the wrapper comes from kubernetes-sigs while the module file requires the iptables library under the coreos organization, a path that has not moved in a long time.

Release cadence and what it means for upgrades

Three releases are visible and the gaps between them are wide: 0.5.0 in April 2022, 0.6.0 in April 2023, and 0.7.0 in March 2026. Two of those three years-long gaps mean an upgrade can be a large jump rather than a patch.

What each one did is informative. 0.5.0 added nftables support, described as correct support for firewall rules where before only the legacy iptables API was used, which the notes say could mean networking not working as expected on nftables systems. 0.6.0 focused on reducing the number of iptables operations and reordered encapsulation rules for performance, and added initial Cilium compatibility. 0.7.0 is the one to read closely if you run Cilium, because it rewrote that compatibility layer, corrected default route sources, and improved private IP detection.

The last push to the default branch was 2026-09-13, which is after the 0.7.0 tag. So there is unreleased work on main, and the version you can install from a manifest URL is the main branch, not a tag. Pin deliberately: apply a manifest from a tag if you want the behavior the release notes describe.

The module file requires Go 1.25, and the Kubernetes libraries are at 0.35. Those numbers set the floor for building from source, and the WireGuard control library is pinned to a pseudo-version from 2021, which tells you the project froze its dependency on that side rather than tracking it.

Editorial conclusion

Adopt Kilo when you have nodes in more than one location, need a fully connected encrypted layer 3 network between them, and want to keep whatever CNI you already run inside each location. Do not adopt it to replace a cluster CNI, since add-on mode delegates all pod networking to your existing provider and only Kilo connects the locations. Verify two things before you plan the deployment: read the manifests directory for the flavor of CNI you run, because the README names Flannel for add-on mode while the 0.7.0 release notes describe a rewritten Cilium compatibility layer, and check that your nodes have the WireGuard kernel module, since the userspace fallback is the alternative and it is a different performance story.

Frequently asked questions

How do I install Kilo on Kubernetes?

Apply the custom resource definitions and then the manifest for how your cluster was bootstrapped, both from the repository manifests directory. The README ships variants for kubeadm, bootkube, Typhoon and k3s. Nodes also need the WireGuard kernel module loaded and an open UDP port, 51820 by default.

Does Kilo replace my cluster CNI?

Not in add-on mode. There Kilo adds the network between locations and the VPN while your existing provider keeps handling CNI management and local pod networking, so a Flannel cluster stays a Flannel cluster inside each location. In standalone mode Kilo acts as the complete networking provider and installs its own CNI plugins.

Can I connect a laptop or another machine to a Kilo mesh?

Yes, by declaring a Peer resource with an allowedIPs scope and a public key, then rendering the WireGuard configuration with kgctl showconf and applying it to a local interface with wg setconf. The second step needs root and an interface that already exists, which the README does not document creating for you.

Official sources

  1. License: Apache-2.0
  2. Project website
  3. README
  4. Releases
  5. squat/kilo on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/squat-kilo.svg)](https://hysenlabs.com/projects/squat-kilo)

Was this useful?