{"id":47350010,"url":"https://github.com/jacaudi/cloudflare-operator","last_synced_at":"2026-05-06T06:05:45.488Z","repository":{"id":343798443,"uuid":"1176315786","full_name":"jacaudi/cloudflare-operator","owner":"jacaudi","description":"A Kubernetes operator for managing Cloudflare resources declaratively via CRDs. Supports DNS records, zones, zone settings, tunnels, and WAF rulesets.","archived":false,"fork":false,"pushed_at":"2026-04-26T21:39:25.000Z","size":664,"stargazers_count":0,"open_issues_count":8,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-26T22:06:27.339Z","etag":null,"topics":["cloudflare","controller-runtime","dns","golang","infrastructure-as-code","kubebuilder","kubernetes","operator","renovate"],"latest_commit_sha":null,"homepage":null,"language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jacaudi.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-03-08T22:26:05.000Z","updated_at":"2026-04-26T21:39:29.000Z","dependencies_parsed_at":null,"dependency_job_id":"ac19ef41-cdb6-4828-8a46-dae4a7fa2d26","html_url":"https://github.com/jacaudi/cloudflare-operator","commit_stats":null,"previous_names":["jacaudi/cloudflare-operator"],"tags_count":8,"template":false,"template_full_name":null,"purl":"pkg:github/jacaudi/cloudflare-operator","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jacaudi%2Fcloudflare-operator","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jacaudi%2Fcloudflare-operator/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jacaudi%2Fcloudflare-operator/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jacaudi%2Fcloudflare-operator/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jacaudi","download_url":"https://codeload.github.com/jacaudi/cloudflare-operator/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jacaudi%2Fcloudflare-operator/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32487746,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-30T13:12:12.517Z","status":"online","status_checked_at":"2026-05-01T02:00:05.856Z","response_time":64,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cloudflare","controller-runtime","dns","golang","infrastructure-as-code","kubebuilder","kubernetes","operator","renovate"],"created_at":"2026-03-17T23:41:56.183Z","updated_at":"2026-05-06T06:05:45.467Z","avatar_url":"https://github.com/jacaudi.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# cloudflare-operator\n\nA Kubernetes operator that manages Cloudflare resources declaratively via Custom Resources. Define DNS records, tunnels, security + transform rulesets, zone settings, and zone lifecycle as Kubernetes objects with drift detection and automatic reconciliation.\n\n\u003e **Unofficial — community project.** This is not an official Cloudflare product and is not endorsed by or affiliated with Cloudflare, Inc. The operator implements its Cloudflare API access on top of the official [`cloudflare/cloudflare-go`](https://github.com/cloudflare/cloudflare-go) Go SDK; the Cloudflare name and trademarks belong to Cloudflare, Inc. Use at your own discretion.\n\n## Custom Resources\n\n| CRD | Purpose |\n|-----|---------|\n| `CloudflareZone` | Onboard and manage domain lifecycle (create, adopt, activate, delete) |\n| `CloudflareDNSRecord` | Manage DNS records (A, AAAA, CNAME, SRV, MX, TXT, NS) with dynamic IP support |\n| `CloudflareTunnel` | Create tunnels, auto-generate `cloudflared` credentials Secrets, and (optionally) reconcile the operator-managed cloudflared Deployment + ingress config |\n| `CloudflareTunnelRule` | Author a single hostname → backend ingress rule for a tunnel (also emitted automatically by the Gateway API / Service source controllers) |\n| `CloudflareZoneConfig` | Declaratively configure zone settings (SSL, security, performance, network, DNS) |\n| `CloudflareRuleset` | Manage a zone's phase entrypoint ruleset (security / custom rules, rate limiting, transforms, redirects, …) across 14+ Rulesets-Engine phases |\n\nFor end-to-end walkthroughs and topical guides, see [`docs/README.md`](docs/README.md). For field-by-field specs, see [`docs/crd-reference.md`](docs/crd-reference.md).\n\n## Quickstart\n\nNew to cloudflare-operator? Start here: **[docs/domain-onboarding.md](docs/domain-onboarding.md)** — an end-to-end walkthrough from creating an API token to a first workload served via tunnel.\n\nAlready familiar with the CRDs? See:\n- [Gateway API + Service annotations](docs/gateway-api-source.md) — the primary user interface in v1.\n- [Tunnels](docs/tunnels.md) — tunnel CRDs and the operator-managed cloudflared runtime.\n- [CRD reference](docs/crd-reference.md) — every field on every CRD.\n- [Migrating from external-dns](docs/external-dns-migration.md).\n- [Troubleshooting](docs/troubleshooting.md).\n\n## Installation\n\n### Prerequisites\n\n- Kubernetes 1.28+\n- Helm 3.8+ (for OCI chart support)\n- A [Cloudflare API token](https://dash.cloudflare.com/profile/api-tokens) with permissions for the resources you plan to manage (see [authentication](docs/crd-reference.md#authentication))\n\n### 1. Install the operator\n\nThe Helm chart is published as an OCI artifact to GHCR. It installs the CRDs, the controller Deployment, and RBAC.\n\n```sh\nhelm install cloudflare-operator \\\n  oci://ghcr.io/jacaudi/charts/cloudflare-operator \\\n  --version 0.8.0 \\\n  --namespace cloudflare-operator \\\n  --create-namespace\n```\n\nOverride defaults with `--set` or `-f values.yaml`. Common values (see [`chart/values.yaml`](chart/values.yaml)):\n\n```yaml\nimage:\n  tag: \"\"               # defaults to chart appVersion\ncontroller:\n  replicas: 1\nleaderElection:\n  enabled: true         # required if replicas \u003e 1\nmetrics:\n  serviceMonitor:\n    enabled: false      # set true if you run the Prometheus Operator\n```\n\n### 2. Create the credentials Secret\n\n```sh\nkubectl create secret generic cloudflare-api-token \\\n  --namespace cloudflare-operator \\\n  --from-literal=apiToken=\u003cyour-cloudflare-api-token\u003e \\\n  --from-literal=accountID=\u003cyour-cloudflare-account-id\u003e\n\nkubectl label secret cloudflare-api-token \\\n  --namespace cloudflare-operator \\\n  cloudflare.io/managed=true\n```\n\nEvery CR references this Secret via `secretRef.name`. Place the Secret in the same namespace as the CRs that use it. `accountID` is required for `CloudflareZone` and `CloudflareTunnel`; other CRs only read `apiToken`.\n\nThe `cloudflare.io/managed=true` label is required: the operator's manager cache filters Secrets by this label so it only loads Secrets you've explicitly opted in. A Secret without the label produces `Ready=False` with `Reason=SecretNotLabeled` on any CR that references it. To stage a migration across many existing Secrets, set the chart value `secretCacheLabelSelector: \"\"` (or the env `SECRET_CACHE_LABEL_SELECTOR=\"\"`) to disable the filter, label your Secrets, then restore the default. The operator-owned tunnel credentials Secret is auto-labeled.\n\n### 3. Onboard your zone\n\n`CloudflareZone` both creates new zones and adopts existing ones, so this works whether the domain is already in Cloudflare or not. Other CRs reference it via `zoneRef` instead of a raw zone ID.\n\n```yaml\napiVersion: cloudflare.io/v1alpha1\nkind: CloudflareZone\nmetadata:\n  name: example-com\n  namespace: cloudflare-operator\nspec:\n  name: \"example.com\"\n  deletionPolicy: Retain   # leaves the zone in Cloudflare on CR delete\n  secretRef:\n    name: cloudflare-api-token\n```\n\n```sh\nkubectl apply -f zone.yaml\nkubectl get cloudflarezone -n cloudflare-operator\n```\n\nFor new zones, `status.nameServers` lists the nameservers to configure at your registrar. `Ready=True` once the zone is active.\n\n### 4. Create a DNS record\n\n```yaml\napiVersion: cloudflare.io/v1alpha1\nkind: CloudflareDNSRecord\nmetadata:\n  name: homelab\n  namespace: cloudflare-operator\nspec:\n  zoneRef:\n    name: example-com      # the CloudflareZone above (same namespace)\n  name: \"home.example.com\"\n  type: A\n  dynamicIP: true          # auto-resolves and tracks your external IP\n  proxied: true\n  ttl: 1                   # automatic\n  interval: 5m             # drift-check cadence\n  secretRef:\n    name: cloudflare-api-token\n```\n\n```sh\nkubectl apply -f dns-record.yaml\nkubectl describe cloudflarednsrecord homelab -n cloudflare-operator\n```\n\n`Ready=True` means the record is in sync with Cloudflare. Prefer `zoneRef` — the controller resolves the zone ID from status and waits for the zone to be ready. `zoneID: \"\u003cid\u003e\"` is still supported for standalone cases.\n\nMore examples — CNAME, SRV, tunnels, rulesets, zone settings — live in [`config/samples/`](config/samples) and [`docs/crd-reference.md`](docs/crd-reference.md).\n\n## Upgrading\n\n```sh\nhelm upgrade cloudflare-operator \\\n  oci://ghcr.io/jacaudi/charts/cloudflare-operator \\\n  --version \u003cnew-version\u003e \\\n  --namespace cloudflare-operator\n```\n\nHelm does not upgrade CRDs on `helm upgrade`. When a release changes CRD schemas, reapply them first:\n\n```sh\nhelm pull oci://ghcr.io/jacaudi/charts/cloudflare-operator --version \u003cnew-version\u003e --untar\nkubectl apply -f cloudflare-operator/crds/\n```\n\nCheck [`CHANGELOG.md`](CHANGELOG.md) for breaking changes before upgrading.\n\n### Stuck-deleting CRs\n\nIf a CR is stuck deleting because the credentials Secret it references is missing or unlabeled, the operator's `Ready` condition will surface `Reason=SecretNotFound` or `Reason=SecretNotLabeled`. The `Condition.Message` no longer carries the manual-finalizer guidance for credential-load failures during delete (it does on remote-API delete failures); check the operator logs for `\"Remove the finalizer manually to force deletion\"`. To unstick, label the Secret (or set the chart's `secretCacheLabelSelector: \"\"` to disable the filter) so the credential load succeeds, then let the finalizer drain. As a last resort, `kubectl patch` to remove the finalizer manually.\n\n## Uninstall\n\n```sh\nkubectl delete cloudflarednsrecord,cloudflarezone,cloudflaretunnel,cloudflarezoneconfig,cloudflareruleset --all -A\nhelm uninstall cloudflare-operator --namespace cloudflare-operator\nkubectl delete crd \\\n  cloudflarednsrecords.cloudflare.io \\\n  cloudflarezones.cloudflare.io \\\n  cloudflaretunnels.cloudflare.io \\\n  cloudflarezoneconfigs.cloudflare.io \\\n  cloudflarerulesets.cloudflare.io\n```\n\nDelete the CRs **before** uninstalling the chart so finalizers can run. `CloudflareZone` defaults to `deletionPolicy: Retain`, which leaves zones intact in Cloudflare.\n\n## Development\n\nClone the repo and run the controller against your current kube context:\n\n```sh\nmake install        # apply CRDs\nmake run            # run controller locally\nmake test           # unit tests (fake-client based)\nmake lint           # golangci-lint\n```\n\nBuild and deploy a local image to a Kind cluster:\n\n```sh\nmake docker-build IMG=cloudflare-operator:dev\nkind load docker-image cloudflare-operator:dev\nmake deploy IMG=cloudflare-operator:dev\n```\n\nSee [`AGENTS.md`](AGENTS.md) for project conventions (kubebuilder layout, generated files, controller patterns).\n\n## Documentation\n\n- [`docs/README.md`](docs/README.md) — topical-doc index (onboarding, Gateway API, tunnels, migration, troubleshooting)\n- [`docs/crd-reference.md`](docs/crd-reference.md) — field-by-field CRD reference\n- [`config/samples/`](config/samples) — runnable sample manifests for each CRD\n- [`chart/values.yaml`](chart/values.yaml) — all Helm chart configuration options\n- [`CHANGELOG.md`](CHANGELOG.md) — release notes\n\n## License\n\nCopyright 2026. Licensed under the Apache License, Version 2.0. See [LICENSE](LICENSE).\n\n\"Cloudflare\" and the Cloudflare logo are trademarks of Cloudflare, Inc. This project is not endorsed by or affiliated with Cloudflare, Inc. Cloudflare API access is implemented via the official [`cloudflare/cloudflare-go`](https://github.com/cloudflare/cloudflare-go) Go SDK.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjacaudi%2Fcloudflare-operator","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjacaudi%2Fcloudflare-operator","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjacaudi%2Fcloudflare-operator/lists"}