Hosting Game Servers on Bare Metal Kubernetes with Cilium as CNI
Sat 21 March 2026Last year I switched to Cilium as CNI for my Kubernetes clusters. That proved to be a very good experience in overall. And I am still very happy about the features Cilium provides and did not encounter any issues so far. As I threw out kube-vip and wrote an article about UDP specifics for game server hosting with it, I also want to share my experience for hosting my game servers using Cilium.

Rust and Valheim Dedicated Game Servers
I am running Rust and Valheim dedicated servers. For these game servers I created Docker images and Helm charts to run them on Kubernetes. You can check out these projects on GitHub. Feel free to use them:
Rust is a rather old game, but remains extremely popular and is still in Steam's top 10 of most played games. I really like the survival scenario and freedom in gameplay that Rust offers. Valheim is less popular but offers a really nice and relaxed PvE experience when playing together on your own server with a couple of friends.
With my experience using kube-vip and Cilium I shaped those two Helm charts so you have your game server up and running in minutes.
UDP Protocol
As dedicated game servers usually use UDP protocol for their client communication. Kubernetes Ingress does not support UDP protocol only TCP. And the stable branch of Gateway API does not support UDPRoute on the standard channel yet. Therefore, it's pointless to utilize these Ingress or Gateway API for game server hosting.
Cilium L2 Announcements and L2 Aware Load Balancer
When you run your Kubernetes cluster on bare metal with Cilium you usually want to make use of L2 Announcements and the L2 Aware load balancer as you are lacking a cloud provider's load balancer. Also, you want to use Cilium's LoadBalancer IP Address Management (LB IPAM).
So you typically configure a CiliumL2AnnouncementPolicy like this:
apiVersion: cilium.io/v2alpha1
kind: CiliumL2AnnouncementPolicy
metadata:
name: default
spec:
externalIPs: true
loadBalancerIPs: true
And at least one CiliumLoadBalancerIPPool for exposing your game servers:
apiVersion: cilium.io/v2
kind: CiliumLoadBalancerIPPool
metadata:
name: external
spec:
serviceSelector:
matchExpressions:
- key: "lb-ip-pool"
operator: In
values:
- external
blocks:
- start: "192.168.10.50"
stop: "192.168.10.99"
Service Configuration
For hooking up your game server to the network you need to use a Kubernetes Service object
of type LoadBalancer. You need to configure it, so the
Cilium IPAM load balancer picks it up and assigns an external IP
address to it.
Be aware of three Cilium peculiarities for configuring your Service:
externalTrafficPolicyneeds to be set toClusteras L2 Announcements does not supportLocalas policy- label the Service for controlling the
CiliumLoadBalancerIPPoolselection with service selectors - annotate the Service with
lbipam.cilium.io/ipsto request a stable IP address for your game server
With labeling your services you can keep tight control over the IP pools you want to use for your game servers. And for your network routing configuration you usually need stable IP addresses of your game servers.
A full Service example could look like this for a Rust dedicated server:
apiVersion: v1
kind: Service
metadata:
name: rust
labels:
lb-ip-pool: external
annotations:
lbipam.cilium.io/ips: 192.168.10.55
spec:
type: LoadBalancer
externalTrafficPolicy: Cluster
ports:
- name: server-port
port: 28015
targetPort: server-port
protocol: UDP
- name: rcon-port
port: 28016
targetPort: rcon-port
protocol: TCP
- name: query-port
port: 28017
targetPort: query-port
protocol: UDP
- name: app-port
port: 28082
targetPort: app-port
protocol: TCP
selector:
app.kubernetes.io/name: rust