External DNS records with Crossplane and Cloudflare
This document explains how I deploy Crossplane with the Cloudflare provider to manage external CNAME DNS records in my GitOps workflow.
About external DNS records with Crossplane and Cloudflare
Crossplane transforms cloud services into Kubernetes APIs, enabling GitOps friendly management of DNS entries. By installing the Crossplane control plane, configuring the Cloudflare provider, and declaring Record resources, we automate the lifecycle of CNAME records.
Maintain manifest definitions in k8s/infrastructure/controllers/crossplane/ and each application’s directory to prevent documentation drift.
More details about the Cloudflare provider setup
-
Crossplane chart Deploys Crossplane into the
crossplane-systemnamespace to enable its CRDs and controllers. -
ExternalSecret Retrieves the Cloudflare API token from a Bitwarden backed
ClusterSecretStoreand templates a Kubernetes Secret containing acredsfield with JSON credentials. -
Provider Installs the Crossplane Cloudflare provider package.
-
ProviderConfig References the synced Secret's
credskey to supply credentials to the provider.
Prerequisites
- A Kubernetes cluster with
kubectlaccess. - A
ClusterSecretStorecontaining a valid Cloudflare API token under the keyinfra-cloudflare-api-token. - GitOps tooling (Argo CD, Flux, etc.) configured to apply this repository.
Overview of steps
- Deploy Crossplane: Install the Crossplane Helm chart into
crossplane-system. - Fetch Cloudflare token: Create an
ExternalSecretto sync the API token into a Secret. - Install Cloudflare provider: Apply the
ProviderandProviderConfigmanifests. - Declare DNS records: Add
dns-record.yamland updatekustomization.yamlin each service repo. - Apply and verify: Deploy manifests and confirm records in Crossplane and Cloudflare.
Deploy Crossplane and configure the Cloudflare provider
-
Install Crossplane chart Apply the official Crossplane Helm chart into
crossplane-systemvia your GitOps tool. -
Configure external secrets Define an
ExternalSecretpointing to the Bitwarden backedClusterSecretStoreto syncinfra-cloudflare-api-tokenandinfra-cloudflare-account-idinto a Kubernetes Secret namedcloudflare-api-token. The secret includes acredskey containing{ "api_token": "...", "account_id": "..." }. -
Install provider and credentials Apply the Crossplane
Providerfor Cloudflare and aProviderConfigthat references thecredskey incloudflare-api-token.
Apply DNS records for each service
Each application repository should include:
-
A
dns-record.yamlmanifest declaring aRecord(typeCNAME) with:metadata.nameandmetadata.namespacematching the service.spec.forProvider.nameas the hostname (e.g.,frigate).spec.forProvider.valueas the Cloudflare Tunnel target.
-
An update to
kustomization.yamladdingdns-record.yamlto the resources list.
Repeat for Immich, Jellyfin, Jellyseerr, Baby Buddy, IT Tools, Authentik, etc., adjusting names and namespaces per service.
Verify the configuration
-
Crossplane resources Run
kubectl get records.dns.cloudflare.upbound.io -Aand confirm each record’s status isREADY. -
Cloudflare dashboard In Cloudflare UI, go to DNS → your.domain.tld and verify that CNAME entries point to the correct Tunnel hostnames.