inlets-operator: public TCP LoadBalancers for local Kubernetes
Get public TCP LoadBalancers for local Kubernetes clusters
At a glance
- What is it?
- inlets-operator gives Kubernetes Services of type LoadBalancer a public TCP endpoint by provisioning a cloud VM, running inlets across the tunnel, and writing its IP back to the Service. It fits local clusters that need Kubernetes-native ingress without a managed cloud load balancer.
- Who is it for?
- Adopt inlets-operator when a local, private, or homelab Kubernetes cluster needs public TCP ingress and cloud VM costs are acceptable. Do not choose it for UDP-only exposure, immutable workloads, or environments where cloud provisioning is prohibited.
- Can I use it commercially?
- Yes. MIT 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 received new commits within the last day.
- 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
Why inlets-operator exists for clusters outside managed clouds
Managed Kubernetes engines commonly turn a Service of type LoadBalancer into a public TCP endpoint by asking the cloud provider for a load balancer. A local cluster often lacks that control plane integration, even when it runs Kubernetes normally and supports the same Service API. inlets-operator fills that gap for developers operating a laptop cluster, an on-premises installation, a VM-based lab, a Raspberry Pi, or a private-cloud environment.
The intended users are engineers who need ingress for self-hosted applications, APIs, webhooks, colleague previews, or client demonstrations. The README positions it as a way to obtain a public IP without opening a firewall port, configuring router port forwarding, or setting up dynamic DNS. That framing matters because the project is not a local ingress controller. It places the public endpoint on a separately provisioned cloud VM and connects that VM to the cluster through inlets.
The Kubernetes-facing behavior remains familiar. Once the operator is installed, an eligible Service of type LoadBalancer moves from a pending external address to a real public IP. Existing Ingress Controllers and Istio Ingress Gateways can sit behind the TCP endpoint, although the operator itself operates at the Service and Tunnel resource level rather than implementing HTTP routing rules.
The Service, Tunnel, client, and exit-server lifecycle
The operator watches Kubernetes resources and creates a Tunnel custom resource for each managed Service. The README shows a `Tunnel` named after the Service, with fields linking it to that Service and reporting the host IP, host ID, and status. A newly observed tunnel begins in `provisioning` and can later report an `active` host address.
The public side is a cloud VM running an inlets server process. Within the cluster, the operator runs an inlets client as a Deployment. After the client and server connect, the operator updates the original Service with the exit server's public IP. From Kubernetes clients, this resembles a cloud-provider-managed LoadBalancer even though the networking path crosses a tunnel.
Resource ownership also drives cleanup. Deleting the Service or annotating it causes the cloud VM to be deleted, according to the README. This is a consequential design choice rather than a minor convenience. A tunnel can represent billable infrastructure, and Kubernetes deletion semantics are being tied to removal of a remote compute resource.
The operator can selectively ignore Services. Setting `operator.inlets.dev/manage` to `0` excludes a particular Service. Alternatively, installing the Helm chart with `annotatedOnly: true` limits automatic management to Services carrying a management annotation. This is useful when a cluster also uses another LoadBalancer provider, but it makes annotation policy part of the operational contract.
Installing inlets-operator and exposing the first Service
The README does not give one universal installation command. It directs users to the provider-specific installation documentation at `https://docs.inlets.dev/reference/inlets-operator/` and points to the Helm chart under `chart/inlets-operator/`. That matters because the operator must be configured to provision a VM in a supported cloud, and the repository information available here does not document those credentials, chart values, or provider-specific steps. No image pull command is invented here.
After following the provider installation instructions and confirming that the operator is running, the documented first use is to create an Nginx Pod and expose it as a LoadBalancer Service:
kubectl run nginx-1 --image=nginx --port=80 --restart=Always
kubectl expose pod/nginx-1 --port=80 --type=LoadBalancerThe first command starts Nginx in a Pod named `nginx-1`. The second creates a Service whose external address can initially be `<pending>`. Watch the Service until the operator updates it:
kubectl get services -wThe documented output changes from `<pending>` under `EXTERNAL-IP` to a public address. To inspect the custom resource created for the Service, run:
kubectl get tunnelsIts `HOSTSTATUS` should progress from `provisioning` to `active`, and `HOSTIP` should contain the public address. The values shown in the README are examples, not fixed outputs. If an IPVS cluster does not react through the LoadBalancer field, the documented path is to declare a `Tunnel` resource directly:
apiVersion: operator.inlets.dev/v1alpha1
kind: Tunnel
metadata:
name: nginx-1-tunnel
namespace: default
spec:
serviceRef:
name: nginx-1
namespace: default
status: {}The tunnel then references `nginx-1` explicitly. This distinction is a genuine IPVS limitation rather than a variant installation step.
Service annotations and the irreversible proxy protocol choice
The annotations provide control over which Services are managed and how the exit server handles source addresses. To exclude one Service while retaining automatic management for others, apply:
kubectl annotate service nginx-1 operator.inlets.dev/manage=0If the Helm chart was installed with `annotatedOnly: true`, management is inverted at cluster scope. Individual Services must then opt in:
kubectl annotate service nginx-1 operator.inlets.dev/manage=1Proxy Protocol support can preserve and forward the original client IP to Services. The `operator.inlets.dev/proxy-proto` annotation accepts `v1`, `v2`, or an empty value to disable it. For example:
kubectl annotate service nginx-1 operator.inlets.dev/proxy-proto=v2The important constraint appears in the README's warning: this setting is applied when the exit-server VM is provisioned and cannot be changed afterward. Changing it for an existing Service requires deleting the Service, which also deletes its tunnel and VM, and then recreating the Service with the new annotation. That makes an apparently simple annotation change a replacement operation involving external infrastructure.
A predefined token can be supplied through `authTokenRef` in the `Tunnel` resource:
spec:
authTokenRef:
name: nginx-1-tunnel-token
namespace: defaultThe README documents the reference shape but does not explain token generation or secret creation in the available text.
Cloud cost, multi-architecture builds, and release coupling
inlets-operator is not a self-contained local networking process. Its normal exposure path creates a public-cloud VM, so adoption includes cloud account configuration, provider availability, and the VM's running cost. The README states that roughly a dozen exit-server clouds are supported and describes DigitalOcean and Hetzner examples. Those provider statements are not substitutes for checking current installation documentation, especially because the README labels its Hetzner example as coming soon.
The repository includes a Helm chart, Go controller sources, vendored dependencies, and a multi-stage Dockerfile. The operator image is documented as supporting both `x86_64` and `arm64`. The build uses Docker buildx, runs `go test -v ./...`, performs a license check, and then builds a static binary for `gcr.io/distroless/static:nonroot`. These details indicate that upgrades involve Kubernetes controller code, its CRD behavior, cloud-provision dependencies, and the inlets server version.
Release notes make that coupling visible. Version `0.17.23`, dated 2026-08-18, updated `inlets-pro` to `0.11.14`. Version `0.17.22`, dated 2026-04-08, updated the vendor tree and inlets-pro image version, while `0.17.21`, dated 2026-02-25, added Proxy Protocol support for tunnel servers. The last push was on 2026-08-18, so there is current repository activity, but upgrades can still carry infrastructure changes rather than being limited to controller code.
The repository is licensed under MIT. That permits broad use, modification, distribution, and sublicensing subject to the license text and included notices. This is not legal advice, and operators should retain the MIT license and attribution files when redistributing the software or image.
Operational limits and a different approach with inlets
The clearest boundary is that the project exposes TCP traffic, not arbitrary UDP services. The README consistently describes TCP LoadBalancers and the Service example uses `80/TCP`. It does not document UDP transport through this operator, so a cluster requiring public UDP ingress should not infer support from the generic tunneling topic.
Proxy Protocol also imposes application-layer compatibility. The documentation describes preserving and forwarding the original client IP, but it does not say that every backend can consume Proxy Protocol automatically. Operators would need to verify how a particular Service handles the selected protocol. More importantly for Kubernetes lifecycle management, the setting is fixed at VM creation.
The real alternative is to run inlets directly without this operator. The operator uses inlets client and server components. Compared with managing those components directly, inlets-operator adds the Kubernetes control loop. inlets-operator adds the Kubernetes control loop: it watches Services, creates Tunnel resources, provisions exit servers, writes addresses back to Services, and ties Service removal or annotation changes to VM deletion. The trade-off is automation and Kubernetes compatibility in exchange for a cloud VM, provider integration, and an external resource lifecycle.
Editorial conclusion
Adopt inlets-operator when a local, private, or homelab Kubernetes cluster needs public TCP ingress and cloud VM costs are acceptable. Do not choose it for UDP-only exposure, immutable workloads, or environments where cloud provisioning is prohibited. Verify first that the selected provider is supported, the required credentials and chart settings match your cluster, and deletion will remove the tunnel VM as documented.
Frequently asked questions
How do I expose a Kubernetes Service with inlets-operator?
Install the operator using the provider-specific instructions, then create a Service of type LoadBalancer. The README documents this with an Nginx Pod exposed on TCP port 80, after which the Service's external IP changes from pending to a public address.
How can I stop inlets-operator from managing one Service?
Set the `operator.inlets.dev/manage` annotation to `0`. For cluster-wide opt-in behavior, install the Helm chart with `annotatedOnly: true` and set the annotation to `1` on Services that should be managed.
Can I change Proxy Protocol after the tunnel is created?
No. The proxy protocol setting is applied when the exit-server VM is provisioned. The README says the Service, tunnel, and VM must be deleted and recreated with the desired annotation.
Does inlets-operator work with an IPVS Kubernetes network?
Yes, but the README says you must declare a `Tunnel` custom resource instead of relying on the Service's LoadBalancer field. Its `serviceRef` must identify the target Service and namespace.
Official sources
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.
[](https://hysenlabs.com/projects/inlets-inlets-operator)Was this useful?