Skip to content

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, and each replica opens an independent connection for high availability.

cloudflared replaces the Ingress controller for publicly exposed services

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
  • HA by default — 2 replicas, each with an independent Cloudflare edge connection
  • PodDisruptionBudget — built-in disruption protection enabled by default
  • Dashboard-managed routing — Public Hostnames configured in the Cloudflare dashboard
  • Prometheus metrics/ready and /metrics endpoint on port 2000, enabled by default
  • ServiceMonitor — optional Prometheus Operator integration
  • Existing Secret support — bring your own Secret for the tunnel token

Quick Start

  1. Go to Cloudflare Zero Trust dashboardNetworks → Tunnels
  2. Create a new tunnel and copy the token
  3. Deploy this chart with the token
helm install cloudflared helmforge/cloudflared \
  --set tunnel.token='eyJhIjoiY2Y...'
  1. 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 — cloudflared with inline token (not recommended for production)
# Store the token in an existing Secret for production environments.
tunnel:
  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:
  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 — Single replica for dev or resource-constrained environments
tunnel:
  existingSecret: cloudflare-tunnel
  existingSecretKey: token

replicaCount: 1

pdb:
  enabled: false # PDB with minAvailable:1 would block all maintenance on 1-replica deployments

Configuration Reference

Core

ParameterTypeDefaultDescription
nameOverridestring""Override the chart name.
fullnameOverridestring""Override the full release name.
commonLabelsobject{}Extra labels added to all resources.

Image

ParameterTypeDefaultDescription
image.repositorystringdocker.io/cloudflare/cloudflaredcloudflared container image.
image.tagstring"2026.3.0"Image tag.
image.pullPolicystringIfNotPresentImage pull policy.
imagePullSecretsarray[]Pull secrets for private registries.

Tunnel

ParameterTypeDefaultDescription
tunnel.tokenstring""Tunnel token from the Cloudflare dashboard. Prefer existingSecret in production.
tunnel.existingSecretstring""Existing Kubernetes Secret containing the tunnel token.
tunnel.existingSecretKeystringtokenKey inside the existing Secret for the token value.
Do not store the tunnel token inline in values.yaml for production

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

ParameterTypeDefaultDescription
cloudflared.logLevelstringinfoLog verbosity: info, debug, warn, error, or fatal.
cloudflared.noAutoupdatebooleantrueDisable in-process auto-update. Always true in containers.
cloudflared.metricsPortinteger2000Port serving /ready and /metrics.
cloudflared.extraArgsarray[]Extra command-line arguments appended to the cloudflared command.
cloudflared.extraEnvarray[]Extra environment variables for the container.

Replicas and Availability

ParameterTypeDefaultDescription
replicaCountinteger2Number of cloudflared replicas. Each replica opens an independent tunnel connection.
Do not use HPA with cloudflared

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

ParameterTypeDefaultDescription
pdb.enabledbooleantrueCreate a PodDisruptionBudget for cloudflared pods.
pdb.minAvailableinteger1Minimum available replicas during voluntary cluster disruptions.

Service

ParameterTypeDefaultDescription
service.typestringClusterIPKubernetes service type.
service.portinteger2000Metrics service port.
service.annotationsobject{}Annotations for the Service.

Metrics

ParameterTypeDefaultDescription
metrics.enabledbooleantrueExpose the metrics Service for /ready and /metrics.
serviceMonitor.enabledbooleanfalseCreate a Prometheus Operator ServiceMonitor.
serviceMonitor.intervalstring30sMetrics scrape interval.
serviceMonitor.labelsobject{}Extra labels added to the ServiceMonitor.

Probes

ParameterTypeDefaultDescription
probes.startup.enabledbooleantrueEnable startup probe on /ready.
probes.startup.initialDelaySecondsinteger5Startup probe initial delay.
probes.startup.periodSecondsinteger5Startup probe period.
probes.startup.timeoutSecondsinteger3Startup probe timeout.
probes.startup.failureThresholdinteger12Startup probe failure threshold.
probes.liveness.enabledbooleantrueEnable liveness probe on /ready.
probes.liveness.initialDelaySecondsinteger0Liveness probe initial delay.
probes.liveness.periodSecondsinteger15Liveness probe period.
probes.liveness.timeoutSecondsinteger5Liveness probe timeout.
probes.liveness.failureThresholdinteger3Liveness probe failure threshold.
probes.readiness.enabledbooleantrueEnable readiness probe on /ready.
probes.readiness.initialDelaySecondsinteger0Readiness probe initial delay.
probes.readiness.periodSecondsinteger10Readiness probe period.
probes.readiness.timeoutSecondsinteger5Readiness probe timeout.
probes.readiness.failureThresholdinteger3Readiness probe failure threshold.

Resources and Security

ParameterTypeDefaultDescription
resourcesobject{}CPU and memory requests and limits.
podSecurityContextobject{}Pod-level security context.
securityContextobject{}Container-level security context.

Service Account

ParameterTypeDefaultDescription
serviceAccount.createbooleanfalseCreate a dedicated ServiceAccount.
serviceAccount.namestring""Override the ServiceAccount name.
serviceAccount.annotationsobject{}Annotations for the ServiceAccount.

Scheduling

ParameterTypeDefaultDescription
nodeSelectorobject{}Node selector for scheduling.
tolerationsarray[]Tolerations for scheduling.
affinityobject{}Affinity rules.
topologySpreadConstraintsarray[]Topology spread constraints.
priorityClassNamestring""PriorityClass for the pod.
terminationGracePeriodSecondsinteger30Termination grace period.
podLabelsobject{}Extra labels for the pod.
podAnnotationsobject{}Extra annotations for the pod.

Extra

ParameterTypeDefaultDescription
extraVolumesarray[]Extra volumes to attach to the pod.
extraVolumeMountsarray[]Extra volume mounts for the container.
extraManifestsarray[]Extra Kubernetes manifests deployed alongside the chart.

More Information