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.
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
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.
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
The controller resolves Cloudflare API credentials in the following order:
.spec.infrastructure.parametersRef references a
CloudflareGatewayParameters that has .spec.secretRef, credentials are
read from that Secret.parametersRef Secret.If none of the above is set, the Gateway is rejected with
Accepted=False/InvalidParameters.
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.
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.
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.
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.
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.
The controller validates the Gateway spec and annotations on every
reconciliation. If any validation fails, the Gateway is rejected with
Accepted=False.
reconcileEvery annotation value must be a valid Go duration string.
Invalid values are rejected with Accepted=False/InvalidParameters.Accepted=False/ListenersNotValid.protocol must be HTTPS and port must be 443. Other
values are rejected with Accepted=False/ListenersNotValid.tls field must not be set.
Rejected with Accepted=False/ListenersNotValid.hostname field must not be set. Hostnames are configured on
HTTPRoutes and GRPCRoutes instead. Rejected
with Accepted=False/ListenersNotValid.allowedRoutes.kinds is set, it must only contain HTTPRoute and/or
GRPCRoute. Other kinds are rejected with Accepted=False/ListenersNotValid.The .spec.addresses field must not be set. Rejected with
Accepted=False/UnsupportedAddress.
.spec.infrastructure.parametersRef must reference a
CloudflareGatewayParameters. Other kinds are rejected with
Accepted=False/InvalidParameters.CloudflareGatewayParameters, the resource must exist.
Rejected with Accepted=False/InvalidParameters.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:
parametersRef. Rejected with
Accepted=False/InvalidParameters.parametersRef must reference a core/v1 Secret with a namespace.
Rejected with Accepted=False/InvalidParameters.ReferenceGrant.
Rejected with Accepted=False/InvalidParameters.The controller populates .status.addresses with one entry for the managed
tunnel, using type: Hostname and the tunnel’s CNAME target
(<tunnelID>.cfargotunnel.com).
A Gateway enters various states during its lifecycle, reflected as Kubernetes Conditions. It can be accepted, programmed, DNS-managed, or ready.
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:
type: Acceptedstatus: "True"reason: AcceptedWhen the Gateway is not accepted:
type: Acceptedstatus: "False"reason: ListenersNotValid | UnsupportedAddress | InvalidParametersReasons for rejection:
ListenersNotValid: Listener validation failed (e.g. wrong listener count,
unsupported protocol, TLS configured, hostname set on listener).UnsupportedAddress: .spec.addresses is set (not supported).InvalidParameters: CloudflareGatewayParameters could not be read or
validated, or credentials are invalid.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.
type: Programmedstatus: "True"reason: ProgrammedCustom 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):
type: DNSManagementstatus: "True"reason: Enabledmessage: All hostnamesWhen DNS management is enabled for specific zones:
type: DNSManagementstatus: "True"reason: Enabledmessage: lists each allowed zone on its own lineWhen DNS management is disabled:
type: DNSManagementstatus: "False"reason: DisabledCustom 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:
type: Readystatus: "True"reason: ReconciliationSucceededWhen a terminal failure occurs (e.g. the Deployment exceeded its progress deadline, unrecoverable API error):
type: Readystatus: "False"reason: ReconciliationFailedWhen waiting for Deployment(s) to become ready (e.g. during a rolling update):
type: Readystatus: "Unknown"reason: ProgressingWhen transient errors occurred during reconciliation and the controller will retry:
type: Readystatus: "Unknown"reason: ProgressingWithRetry