cloudflare-gateway-controller

Gateway

The controller reconciles Gateway resources whose GatewayClass has .spec.controllerName: cloudflare-gateway-controller.io/controller.

This document covers the annotations recognized by this controller and the conditions it sets. For the full Gateway spec, see the Gateway API documentation.

Example

The following example shows a Gateway that references a CloudflareGatewayParameters for its Cloudflare configuration:

apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: my-gateway
  namespace: default
  annotations:
    cloudflare-gateway-controller.io/reconcile: "enabled"
    cloudflare-gateway-controller.io/reconcileEvery: "10m"
spec:
  gatewayClassName: cloudflare
  infrastructure:
    parametersRef:
      group: cloudflare-gateway-controller.io
      kind: CloudflareGatewayParameters
      name: my-params
  listeners:
    - name: https
      protocol: HTTPS
      port: 443

Writing a Gateway spec

The Gateway must have exactly one listener with protocol: HTTPS and port: 443. The tls field and hostnames on listeners are not supported. Cloudflare handles TLS termination; hostnames are configured on HTTPRoute and GRPCRoute resources instead.

The .spec.addresses field is not supported and must not be set.

Parameters reference

The .spec.infrastructure.parametersRef field references a CloudflareGatewayParameters that provides Cloudflare-specific configuration (credentials, DNS, tunnel):

spec:
  infrastructure:
    parametersRef:
      group: cloudflare-gateway-controller.io
      kind: CloudflareGatewayParameters
      name: my-params

Credentials resolution

The controller resolves Cloudflare API credentials in the following order:

  1. If .spec.infrastructure.parametersRef references a CloudflareGatewayParameters that has .spec.secretRef, credentials are read from that Secret.
  2. Otherwise, credentials are read from the GatewayClass parametersRef Secret.

If none of the above is set, the Gateway is rejected with Accepted=False/InvalidParameters.

Reconciliation control

The cloudflare-gateway-controller.io/reconcile annotation controls whether reconciliation is enabled for the Gateway.

The default value is enabled. Set to disabled to pause reconciliation:

metadata:
  annotations:
    cloudflare-gateway-controller.io/reconcile: "disabled"

When reconciliation is disabled and the Gateway is deleted, the controller removes owner references from managed Kubernetes resources (Deployments, Secrets, ConfigMaps, ServiceAccounts, Roles, RoleBindings) instead of deleting Cloudflare resources, leaving tunnels and DNS records intact.

Reconciliation interval

The cloudflare-gateway-controller.io/reconcileEvery annotation overrides the default periodic reconciliation interval used for drift correction. The value must be a valid Go duration string.

metadata:
  annotations:
    cloudflare-gateway-controller.io/reconcileEvery: "5m"

The default interval is 10m. If the value cannot be parsed, the Gateway is rejected with Accepted=False/InvalidParameters.

On-demand reconciliation

The cloudflare-gateway-controller.io/reconcileRequestedAt annotation triggers an on-demand reconciliation when its value changes. The controller records the handled value in the CloudflareGatewayStatus .status.lastHandledReconcileAt field and only acts on new values.

This annotation is managed by cfgwctl reconcile gateway — do not set it manually.

On-demand token rotation

The gateways.cloudflare-gateway-controller.io/rotateTokenRequestedAt annotation triggers an on-demand tunnel token rotation when its value changes. The controller records the handled value in the CloudflareGatewayStatus .status.tunnel.token.rotation.lastHandledRotateAt field once the rotation is fully complete (all Deployments rolled out with the new token).

This annotation is managed by cfgwctl rotate gateway token — do not set it manually.

Cloudflare resource naming

The controller generates deterministic names for Cloudflare and Kubernetes resources based on the Gateway identity. This enables cluster recreation without leaking Cloudflare resources — a reborn cluster with the same clusterName adopts existing resources instead of creating duplicates.

Resource Name
Cloudflare Tunnel cloudflare-gateway-controller.io/clusters/<clusterName>/namespaces/<namespace>/gateways/<gatewayName>
cloudflared Deployment gateway-<gatewayName>-<replicaName> (default: primary)
VPA (when autoscaling) gateway-<gatewayName>-<replicaName> (default: primary)
Tunnel token Secret gateway-<gatewayName>
Routes ConfigMap gateway-<gatewayName>
Tunnel ServiceAccount gateway-<gatewayName>
Tunnel Role gateway-<gatewayName>
Tunnel RoleBinding gateway-<gatewayName>

clusterName comes from the Helm value config.clusterName (required).

Source: TunnelName(), GatewayResourceName(), GatewayReplicaName() in api/v1/meta_types.go.

Validations

The controller validates the Gateway spec and annotations on every reconciliation. If any validation fails, the Gateway is rejected with Accepted=False.

Annotations

Listeners

Addresses

The .spec.addresses field must not be set. Rejected with Accepted=False/UnsupportedAddress.

Parameters reference

Credentials

The controller must be able to resolve Cloudflare API credentials through the fallback chain. If none of the sources provide credentials, the Gateway is rejected with Accepted=False/InvalidParameters.

When falling back to the GatewayClass parametersRef:

Gateway Status

Addresses

The controller populates .status.addresses with one entry for the managed tunnel, using type: Hostname and the tunnel’s CNAME target (<tunnelID>.cfargotunnel.com).

Conditions

A Gateway enters various states during its lifecycle, reflected as Kubernetes Conditions. It can be accepted, programmed, DNS-managed, or ready.

Accepted Gateway

Standard Gateway API condition. The controller marks a Gateway as accepted when it passes validation.

When the Gateway is accepted, the controller sets a Condition with the following attributes in the Gateway’s .status.conditions:

When the Gateway is not accepted:

Reasons for rejection:

Programmed Gateway

Standard Gateway API condition. Per the spec, Programmed indicates whether configuration has been generated for the underlying data plane — not whether the data plane is operationally healthy (that is reported by the custom Ready condition).

The controller sets Programmed=True once the tunnel exists on Cloudflare and all Kubernetes resources (Deployments, Secrets, ConfigMaps) have been applied. This condition stays True during rolling updates and even when pods are not yet ready, because the configuration has been generated.

DNS Management

Custom condition, not part of the Gateway API spec. Reports whether DNS CNAME record management is enabled for this Gateway.

DNS is enabled by default for all hostnames. It can be restricted to specific zones via .spec.dns.zones in the CloudflareGatewayParameters, or disabled entirely by setting an empty zones list (dns.zones: []).

When DNS management is enabled for all hostnames (no zone restriction):

When DNS management is enabled for specific zones:

When DNS management is disabled:

Ready Gateway

Custom condition, not part of the Gateway API spec. kstatus-compatible condition that summarizes the overall reconciliation state.

When all Cloudflare resources are reconciled and all tunnel Deployments are fully rolled out with the expected token hash:

When a terminal failure occurs (e.g. the Deployment exceeded its progress deadline, unrecoverable API error):

When waiting for Deployment(s) to become ready (e.g. during a rolling update):

When transient errors occurred during reconciliation and the controller will retry: