Expose a service through Cloudflare Tunnel
This content is for the v0.1 version. Switch to the latest version for up-to-date documentation.
Run cloudflared in the cluster when you want Cloudflare Tunnel to reach a
ClusterIP Service. The connector makes outbound connections to Cloudflare, so
the Service does not need an Ingress or LoadBalancer.
This guide assumes you have a Cloudflare account, a domain managed by Cloudflare, and a Kix package that exposes its workload through a Service.
Create a remotely managed tunnel
Section titled “Create a remotely managed tunnel”In the Cloudflare dashboard, open Networking > Tunnels and create a
Cloudflare Tunnel. Select Add a replica and copy the token from the
generated cloudflared installation command. Anyone with this token can run
the tunnel, so handle it as a credential.
Store the token in a SOPS file with one top-level key:
token: eyJ...Encrypt the file before committing it. Follow Use SOPS-backed secrets if the deployment machine is not already configured to decrypt SOPS files.
Add the origin Service
Section titled “Add the origin Service”Add the application package to the cluster. The example package creates
Service/web in the cloudflare-example namespace:
instances.cloudflare-example.web = { package = exposureApp; };The Service remains internal to Kubernetes. Its default cluster address is:
http://web.cloudflare-example.svc.cluster.local:80cluster.local comes from the cluster’s clusterDomain option. Replace the
suffix if your cluster uses a different value. clusterDomain controls
in-cluster DNS and is unrelated to the public domain served by the tunnel.
Add the connector
Section titled “Add the connector”Create the cloudflared instance in its own namespace:
instances.tunnel-system.cloudflared = { package = packages.cloudflared; config = { image = "cloudflare/cloudflared:2026.9.0"; tokenSource = ./cloudflare-token.enc.yaml; replicas = 2; }; };tokenSource must point to a SOPS file whose top-level token key contains
the tunnel token. The example pins the connector image to a specific
cloudflared release;
review and update that version as part of normal dependency maintenance.
Two replicas give the tunnel more than one connector process. Both use the same remotely managed tunnel token.
Configure the public hostname
Section titled “Configure the public hostname”Open the tunnel in the Cloudflare dashboard and add a public hostname. Set its service type to HTTP and its URL to the in-cluster address:
web.cloudflare-example.svc.cluster.local:80This route is part of the remotely managed tunnel configuration. It is not a Kubernetes resource in the Kix cluster artifact.
Check and deploy
Section titled “Check and deploy”Render the relevant resources before contacting the cluster:
❱ kix build how-to-platform-cloudflare-tunnel --output json
[
{
"kind": "Deployment",
"namespace": "tunnel-system",
"name": "cloudflared",
"replicas": 2,
"image": "cloudflare/cloudflared:2026.9.0",
"args": [
"tunnel",
"--no-autoupdate",
"--metrics",
"0.0.0.0:2000",
"run"
]
},
{
"kind": "Secret",
"namespace": "tunnel-system",
"name": "cloudflared-token",
"declaredKeys": [
"token"
],
"plaintextIncluded": false
},
{
"kind": "Service",
"namespace": "cloudflare-example",
"name": "web",
"ports": [
{
"name": "http",
"port": 80,
"protocol": "TCP"
}
]
}
] The excerpt shows the origin Service, the two-replica connector Deployment, and the token Secret without decrypted data.
Make the SOPS identity available and deploy:
❱ export SOPS_AGE_KEY_FILE=/secure/path/age-key.txt❱ kix deploy how-to-platform-cloudflare-tunnel Check the connector pods and the tunnel status:
❱ kubectl get pods -n tunnel-system Cloudflare should show the tunnel as healthy once at least one connector is registered. Test the public hostname from outside the cluster:
❱ curl https://app.example.com Replace app.example.com with the hostname you configured.
Troubleshoot the connector
Section titled “Troubleshoot the connector”Inspect the connector logs when the tunnel remains down or requests return an origin error:
❱ kix logs how-to-platform-cloudflare-tunnel cloudflared --since 10m Check these common causes:
- The SOPS identity cannot decrypt
cloudflare-token.enc.yaml. - The token belongs to another tunnel or has been rotated.
- The public hostname route uses the wrong namespace, Service name, port, or
clusterDomainsuffix. - Network policy blocks DNS or outbound connections from
tunnel-system.
When you rotate the tunnel token, replace the value in the encrypted file and deploy again. Restart the connector pods after the Secret update so each process reads the new token.