Cloudflare Tunnel (cloudflared)
Secure, outbound-only tunnel between your Kubernetes cluster and Cloudflare’s global network. cloudflared connects to Cloudflare from inside the cluster — no open inbound ports, no public IP required. Traffic routing is managed in the Cloudflare dashboard via Public Hostnames. The default install uses a tokenless quick tunnel for demos and smoke tests; production managed tunnels explicitly disable quick tunnel mode and use an existing Secret or tunnel token with HA settings.
When using Cloudflare Tunnel, traffic routing is configured in the Cloudflare dashboard under Networks → Tunnels →
Public Hostnames — not via Kubernetes Ingress resources. Adding a Kubernetes Ingress for the same service duplicates
routing and is usually unnecessary. Services are referenced by their cluster-internal DNS (e.g.
http://my-service.namespace.svc.cluster.local:80).
Key Features
- Zero-trust networking — outbound-only connections, no inbound firewall rules needed
- Tokenless default — quick tunnel mode installs without a Cloudflare token for demos and smoke tests
- HA-ready production — 2+ replicas and PodDisruptionBudget can be enabled for managed tunnels
- Dashboard-managed routing — Public Hostnames configured in the Cloudflare dashboard
- Prometheus metrics —
/readyand/metricsendpoint on port 2000, enabled by default - ServiceMonitor — optional Prometheus Operator integration
- Existing Secret support — bring your own Secret for the tunnel token
- External Secrets Operator — optionally render an
ExternalSecretfor the tunnel token - Quick tunnel mode — ephemeral tunnel mode for demos and smoke tests
- Dual-stack Service fields — optional
ipFamilyPolicyandipFamilies
Quick Start
The chart installs without values by running an ephemeral quick tunnel. For a managed production tunnel:
- Go to Cloudflare Zero Trust dashboard → Networks → Tunnels
- Create a new tunnel and copy the token
- Deploy this chart with the token
helm install cloudflared helmforge/cloudflared \
--set tunnel.quickTunnel.enabled=false \
--set tunnel.token='eyJhIjoiY2Y...'
- In the dashboard, add Public Hostnames mapping your domain to services inside the cluster
(e.g.
https://app.example.com → http://myapp.default.svc.cluster.local:80)
Installation
HTTPS repository:
helm repo add helmforge https://repo.helmforge.dev
helm repo update
helm install cloudflared helmforge/cloudflared -f values.yaml
OCI registry:
helm install cloudflared oci://ghcr.io/helmforgedev/helm/cloudflared -f values.yaml
Deployment Examples
# values.yaml — managed cloudflared with inline token (not recommended for production)
# Store the token in an existing Secret for production environments.
tunnel:
quickTunnel:
enabled: false
token: 'eyJhIjoiY2Y...' # From Cloudflare dashboard
replicaCount: 2
pdb:
enabled: true
minAvailable: 1# values.yaml — Production cloudflared with secret-backed token and Prometheus metrics
# Create the secret first: kubectl create secret generic cloudflare-tunnel --from-literal=token='eyJ...'
tunnel:
quickTunnel:
enabled: false
existingSecret: cloudflare-tunnel
existingSecretKey: token
replicaCount: 2
pdb:
enabled: true
minAvailable: 1
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
memory: 128Mi
metrics:
enabled: true
serviceMonitor:
enabled: true
interval: 30s
labels:
prometheus: kube-prometheus# values.yaml — External Secrets Operator generates the Kubernetes Secret
tunnel:
quickTunnel:
enabled: false
existingSecret: cloudflared-tunnel-token
existingSecretKey: token
externalSecrets:
enabled: true
secretStoreRef:
name: platform-secrets
kind: ClusterSecretStore
data:
- secretKey: token
remoteRef:
key: cloudflared/tunnel
property: token# values.yaml — ephemeral quick tunnel for demos and smoke tests only
tunnel:
quickTunnel:
enabled: true
helloWorld: true
replicaCount: 1
pdb:
enabled: false# values.yaml — Single replica for dev or resource-constrained environments
tunnel:
quickTunnel:
enabled: false
existingSecret: cloudflare-tunnel
existingSecretKey: token
replicaCount: 1
pdb:
enabled: false # PDB with minAvailable:1 would block all maintenance on 1-replica deploymentsConfiguration Reference
Core
| Parameter | Type | Default | Description |
|---|---|---|---|
nameOverride |
string | "" |
Override the chart name. |
fullnameOverride |
string | "" |
Override the full release name. |
commonLabels |
object | {} |
Extra labels added to all resources. |
Image
| Parameter | Type | Default | Description |
|---|---|---|---|
image.repository |
string | docker.io/cloudflare/cloudflared |
cloudflared container image. |
image.tag |
string | "2026.5.2" |
Image tag. |
image.pullPolicy |
string | IfNotPresent |
Image pull policy. |
imagePullSecrets |
array | [] |
Pull secrets for private registries. |
Tunnel
| Parameter | Type | Default | Description |
|---|---|---|---|
tunnel.token |
string | "" |
Tunnel token from the Cloudflare dashboard. Prefer existingSecret in production. |
tunnel.existingSecret |
string | "" |
Existing Kubernetes Secret containing the tunnel token. |
tunnel.existingSecretKey |
string | token |
Key inside the existing Secret for the token value. |
tunnel.quickTunnel.enabled |
boolean | true |
Run an ephemeral quick tunnel for demos and smoke tests. Set false for managed tunnels. |
tunnel.quickTunnel.helloWorld |
boolean | true |
Use cloudflared’s built-in hello-world origin in quick tunnel mode. |
tunnel.quickTunnel.url |
string | http://localhost:8080 |
Origin URL when quick tunnel mode does not use hello-world. |
Setting tunnel.token inline exposes the token in helm get values and Helm release history. Use
tunnel.existingSecret with a pre-created Kubernetes Secret. The token grants full control over the tunnel and cannot
be rotated without updating the Cloudflare dashboard.
cloudflared Options
| Parameter | Type | Default | Description |
|---|---|---|---|
cloudflared.logLevel |
string | info |
Log verbosity: info, debug, warn, error, or fatal. |
cloudflared.noAutoupdate |
boolean | true |
Disable in-process auto-update. Always true in containers. |
cloudflared.metricsPort |
integer | 2000 |
Port serving /ready and /metrics. |
cloudflared.extraArgs |
array | [] |
Extra command-line arguments appended to the cloudflared command. |
cloudflared.extraEnv |
array | [] |
Extra environment variables for the container. |
Replicas and Availability
| Parameter | Type | Default | Description |
|---|---|---|---|
replicaCount |
integer | 1 |
Number of cloudflared replicas. Use 2 or more for production managed tunnels. |
Horizontal Pod Autoscaling that scales down replicas terminates active tunnel connections immediately. Clients
connected through those tunnels will experience dropped connections. Use a fixed replicaCount of 2 or more instead
of autoscaling.
PodDisruptionBudget
| Parameter | Type | Default | Description |
|---|---|---|---|
pdb.enabled |
boolean | false |
Create a PodDisruptionBudget for cloudflared pods. |
pdb.minAvailable |
integer | 1 |
Minimum available replicas during voluntary cluster disruptions. |
Service
| Parameter | Type | Default | Description |
|---|---|---|---|
service.type |
string | ClusterIP |
Kubernetes service type. |
service.port |
integer | 2000 |
Metrics service port. |
service.annotations |
object | {} |
Annotations for the Service. |
service.ipFamilyPolicy |
string | "" |
Optional Service IP family policy for dual-stack. |
service.ipFamilies |
array | [] |
Optional ordered Service IP families. |
External Secrets
| Parameter | Type | Default | Description |
|---|---|---|---|
externalSecrets.enabled |
boolean | false |
Render an External Secrets Operator ExternalSecret. |
externalSecrets.secretStoreRef.name |
string | "" |
SecretStore or ClusterSecretStore name. |
externalSecrets.secretStoreRef.kind |
string | SecretStore |
Secret store kind. |
externalSecrets.data |
array | [] |
Data mappings used to populate the tunnel token Secret. |
Metrics
| Parameter | Type | Default | Description |
|---|---|---|---|
metrics.enabled |
boolean | true |
Expose the metrics Service for /ready and /metrics. |
serviceMonitor.enabled |
boolean | false |
Create a Prometheus Operator ServiceMonitor. |
serviceMonitor.interval |
string | 30s |
Metrics scrape interval. |
serviceMonitor.labels |
object | {} |
Extra labels added to the ServiceMonitor. |
Probes
| Parameter | Type | Default | Description |
|---|---|---|---|
probes.startup.enabled |
boolean | true |
Enable startup probe on /ready. |
probes.startup.initialDelaySeconds |
integer | 15 |
Startup probe initial delay. |
probes.startup.periodSeconds |
integer | 5 |
Startup probe period. |
probes.startup.timeoutSeconds |
integer | 3 |
Startup probe timeout. |
probes.startup.failureThreshold |
integer | 12 |
Startup probe failure threshold. |
probes.liveness.enabled |
boolean | true |
Enable liveness probe on /ready. |
probes.liveness.initialDelaySeconds |
integer | 0 |
Liveness probe initial delay. |
probes.liveness.periodSeconds |
integer | 15 |
Liveness probe period. |
probes.liveness.timeoutSeconds |
integer | 5 |
Liveness probe timeout. |
probes.liveness.failureThreshold |
integer | 3 |
Liveness probe failure threshold. |
probes.readiness.enabled |
boolean | true |
Enable readiness probe on /ready. |
probes.readiness.initialDelaySeconds |
integer | 0 |
Readiness probe initial delay. |
probes.readiness.periodSeconds |
integer | 10 |
Readiness probe period. |
probes.readiness.timeoutSeconds |
integer | 5 |
Readiness probe timeout. |
probes.readiness.failureThreshold |
integer | 3 |
Readiness probe failure threshold. |
Resources and Security
| Parameter | Type | Default | Description |
|---|---|---|---|
resources.requests.cpu |
string | 50m |
Default CPU request. |
resources.requests.memory |
string | 64Mi |
Default memory request. |
resources.limits.cpu |
string | 250m |
Default CPU limit. |
resources.limits.memory |
string | 128Mi |
Default memory limit. |
podSecurityContext |
object | non-root seccomp defaults | Pod-level security context. |
securityContext |
object | non-root read-only defaults | Container-level security context. |
Service Account
| Parameter | Type | Default | Description |
|---|---|---|---|
serviceAccount.create |
boolean | false |
Create a dedicated ServiceAccount. |
serviceAccount.name |
string | "" |
Override the ServiceAccount name. |
serviceAccount.annotations |
object | {} |
Annotations for the ServiceAccount. |
Scheduling
| Parameter | Type | Default | Description |
|---|---|---|---|
nodeSelector |
object | {} |
Node selector for scheduling. |
tolerations |
array | [] |
Tolerations for scheduling. |
affinity |
object | {} |
Affinity rules. |
topologySpreadConstraints |
array | [] |
Topology spread constraints. |
priorityClassName |
string | "" |
PriorityClass for the pod. |
terminationGracePeriodSeconds |
integer | 30 |
Termination grace period. |
podLabels |
object | {} |
Extra labels for the pod. |
podAnnotations |
object | {} |
Extra annotations for the pod. |
Extra
| Parameter | Type | Default | Description |
|---|---|---|---|
extraVolumes |
array | [] |
Extra volumes to attach to the pod. |
extraVolumeMounts |
array | [] |
Extra volume mounts for the container. |
extraManifests |
array | [] |
Extra Kubernetes manifests deployed alongside the chart. |