A Kubernetes operator for Cloudflare.
It manages Cloudflare tunnels, the cloudflared daemons that run them, and DNS records, as Kubernetes resources.
An Ingress controller creates a tunnel for any Ingress in the cloudflare class.
This project is
v1alpha1and the API may change.
You probably want one of these instead:
| Project | Description |
|---|---|
| adyanth/cloudflare-operator | Creates and manages Cloudflare Tunnels and DNS records for HTTP/TCP/UDP Service resources |
| STRRL/cloudflare-tunnel-ingress-controller | A Kubernetes Ingress controller built on Cloudflare Tunnel |
Both are more mature and cover most of what people come here looking for.
This operator exists because I wanted the experience of writing one, and because I wanted my favourite parts of both projects in a single place.
The three model the problem differently.
adyanth's Tunnel and ClusterTunnel bundle the tunnel with the cloudflared Deployment that serves it, and a TunnelBinding attaches Services to a tunnel and creates their DNS records.
STRRL's controller has no CRDs at all: it owns one tunnel for the cluster and derives its configuration from Ingress objects.
Here every Cloudflare object gets its own resource.
A CloudflareTunnel, the Cloudflared that runs it, and a DnsRecord are each reconciled on their own and refer to one another by name.
All resources live in the cloudflare.unmango.dev/v1alpha1 API group.
| Kind | What it does |
|---|---|
CloudflareTunnel |
Creates and reconciles a tunnel through the Cloudflare API, and can create the Cloudflared that runs it |
Cloudflared |
Runs the cloudflared daemon for a tunnel as a DaemonSet or a Deployment |
DnsRecord |
Manages a single DNS record in a zone |
The Ingress controller watches core Ingress objects and creates a CloudflareTunnel for any whose ingressClassName is cloudflare.
It reads its configuration from ingress.cloudflare.unmango.dev/ annotations.
The manager reads its Cloudflare API token from the CLOUDFLARE_API_TOKEN environment variable.
The chart references a Secret holding that token but never creates one, so create it first:
kubectl create namespace cloudflare-operator-system
kubectl create secret generic cloudflare-credentials \
--namespace cloudflare-operator-system \
--from-literal=CLOUDFLARE_API_TOKEN=<token>
helm install cloudflare-operator ./dist/chart \
--namespace cloudflare-operator-system \
--set cloudflare.auth.apiTokenRef.name=cloudflare-credentialsLeaving cloudflare.auth.apiTokenRef unset installs a manager with no credentials.
It still runs and still reconciles Cloudflared, which needs no API access, but every Cloudflare API call fails.
The chart also installs an IngressClass named cloudflare; set ingressClass.enabled=false to skip it.
The CRDs carry helm.sh/resource-policy: keep, so helm uninstall leaves them in the cluster.
helm uninstall cloudflare-operator --namespace cloudflare-operator-systemGateway API support requires the gateway.networking.k8s.io CRDs, which the chart does not ship.
They are large, and a cluster usually installs them once for every controller that implements the API.
make install-gateway-crdsThat applies the standard channel CRDs from the pinned sigs.k8s.io/gateway-api module, so the cluster gets the same version the operator compiles against.
Any other install of the same version works equally well.
GATEWAY_API_CRDS overrides the directory the target applies, for a checkout outside the module cache.
The manager registers no Gateway API controller, so the CRDs on their own change nothing the operator does. They make the types available to the envtest suites and to a cluster preparing for that support.
To install from source without Helm, apply the kustomize output instead. This path does not wire up the token; set it yourself afterwards.
make install
make deploy
make undeploy
make uninstallapiVersion: cloudflare.unmango.dev/v1alpha1
kind: CloudflareTunnel
metadata:
name: example
spec:
accountId: <cloudflare-account-id>
configSource: cloudflare
---
apiVersion: cloudflare.unmango.dev/v1alpha1
kind: Cloudflared
metadata:
name: example
spec:
config:
tunnelRef:
name: exampleMore examples are in config/samples.
The repository is Nix first.
The dev shell supplies Go, controller-gen, kustomize, golangci-lint, kubebuilder, helm, kind and the envtest binaries, so nothing is downloaded at build or test time.
direnv allow # or prefix commands with `nix develop -c`
make test # unit and envtest suites
make lint
make build # nix build .#
make check # nix flake check
make helm # regenerate dist/chartThe container image is built by Nix rather than a Dockerfile:
nix build .#image # produces a script that streams the image to stdout
make kind-load # stream it straight into a kind clusterSee AGENTS.md for the architecture and the reasoning behind the less obvious choices.
Apache 2.0. See LICENSE.