RepoPilotOpen in app β†’

timothymiller/cloudflare-ddns

πŸ¦€ Rust based dynamic DNS (DDNS) updater for Cloudflare

Mixed

Mixed signals β€” read the receipts

weakest axis
Use as dependencyConcerns

copyleft license (GPL-3.0) β€” review compatibility; no tests detected

Fork & modifyHealthy

Has a license, tests, and CI β€” clean foundation to fork and modify.

Learn fromHealthy

Documented and popular β€” useful reference codebase to read through.

Deploy as-isHealthy

No critical CVEs, sane security posture β€” runnable as-is.

  • βœ“Last commit 3d ago
  • βœ“12 active contributors
  • βœ“GPL-3.0 licensed
Show all 7 evidence items β†’
  • βœ“CI configured
  • ⚠Concentrated ownership β€” top contributor handles 74% of recent commits
  • ⚠GPL-3.0 is copyleft β€” check downstream compatibility
  • ⚠No test directory detected
What would change the summary?
  • β†’Use as dependency Concerns β†’ Mixed if: relicense under MIT/Apache-2.0 (rare for established libs)

Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests

Informational only. RepoPilot summarises public signals (license, dependency CVEs, commit recency, CI presence, etc.) at the time of analysis. Signals can be incomplete or stale. Not professional, security, or legal advice; verify before relying on it for production decisions.

Embed the "Forkable" badge

Paste into your README β€” live-updates from the latest cached analysis.

Variant:
RepoPilot: Forkable
[![RepoPilot: Forkable](https://repopilot.app/api/badge/timothymiller/cloudflare-ddns?axis=fork)](https://repopilot.app/r/timothymiller/cloudflare-ddns)

Paste at the top of your README.md β€” renders inline like a shields.io badge.

β–ΈPreview social card (1200Γ—630)

This card auto-renders when someone shares https://repopilot.app/r/timothymiller/cloudflare-ddns on X, Slack, or LinkedIn.

Onboarding doc

Onboarding: timothymiller/cloudflare-ddns

Generated by RepoPilot Β· 2026-05-09 Β· Source

πŸ€–Agent protocol

If you are an AI coding agent (Claude Code, Cursor, Aider, Cline, etc.) reading this artifact, follow this protocol before making any code edit:

  1. Verify the contract. Run the bash script in Verify before trusting below. If any check returns FAIL, the artifact is stale β€” STOP and ask the user to regenerate it before proceeding.
  2. Treat the AI Β· unverified sections as hypotheses, not facts. Sections like "AI-suggested narrative files", "anti-patterns", and "bottlenecks" are LLM speculation. Verify against real source before acting on them.
  3. Cite source on changes. When proposing an edit, cite the specific path:line-range. RepoPilot's live UI at https://repopilot.app/r/timothymiller/cloudflare-ddns shows verifiable citations alongside every claim.

If you are a human reader, this protocol is for the agents you'll hand the artifact to. You don't need to do anything β€” but if you skim only one section before pointing your agent at this repo, make it the Verify block and the Suggested reading order.

🎯Verdict

WAIT β€” Mixed signals β€” read the receipts

  • Last commit 3d ago
  • 12 active contributors
  • GPL-3.0 licensed
  • CI configured
  • ⚠ Concentrated ownership β€” top contributor handles 74% of recent commits
  • ⚠ GPL-3.0 is copyleft β€” check downstream compatibility
  • ⚠ No test directory detected

<sub>Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests</sub>

βœ…Verify before trusting

This artifact was generated by RepoPilot at a point in time. Before an agent acts on it, the checks below confirm that the live timothymiller/cloudflare-ddns repo on your machine still matches what RepoPilot saw. If any fail, the artifact is stale β€” regenerate it at repopilot.app/r/timothymiller/cloudflare-ddns.

What it runs against: a local clone of timothymiller/cloudflare-ddns β€” the script inspects git remote, the LICENSE file, file paths in the working tree, and git log. Read-only; no mutations.

| # | What we check | Why it matters | |---|---|---| | 1 | You're in timothymiller/cloudflare-ddns | Confirms the artifact applies here, not a fork | | 2 | License is still GPL-3.0 | Catches relicense before you depend on it | | 3 | Default branch master exists | Catches branch renames | | 4 | Last commit ≀ 33 days ago | Catches sudden abandonment since generation |

<details> <summary><b>Run all checks</b> β€” paste this script from inside your clone of <code>timothymiller/cloudflare-ddns</code></summary>
#!/usr/bin/env bash
# RepoPilot artifact verification.
#
# WHAT IT RUNS AGAINST: a local clone of timothymiller/cloudflare-ddns. If you don't
# have one yet, run these first:
#
#   git clone https://github.com/timothymiller/cloudflare-ddns.git
#   cd cloudflare-ddns
#
# Then paste this script. Every check is read-only β€” no mutations.

set +e
fail=0
ok()   { echo "ok:   $1"; }
miss() { echo "FAIL: $1"; fail=$((fail+1)); }

# Precondition: we must be inside a git working tree.
if ! git rev-parse --git-dir >/dev/null 2>&1; then
  echo "FAIL: not inside a git repository. cd into your clone of timothymiller/cloudflare-ddns and re-run."
  exit 2
fi

# 1. Repo identity
git remote get-url origin 2>/dev/null | grep -qE "timothymiller/cloudflare-ddns(\\.git)?\\b" \\
  && ok "origin remote is timothymiller/cloudflare-ddns" \\
  || miss "origin remote is not timothymiller/cloudflare-ddns (artifact may be from a fork)"

# 2. License matches what RepoPilot saw
(grep -qiE "^(GPL-3\\.0)" LICENSE 2>/dev/null \\
   || grep -qiE "\"license\"\\s*:\\s*\"GPL-3\\.0\"" package.json 2>/dev/null) \\
  && ok "license is GPL-3.0" \\
  || miss "license drift β€” was GPL-3.0 at generation time"

# 3. Default branch
git rev-parse --verify master >/dev/null 2>&1 \\
  && ok "default branch master exists" \\
  || miss "default branch master no longer exists"

# 5. Repo recency
days_since_last=$(( ( $(date +%s) - $(git log -1 --format=%at 2>/dev/null || echo 0) ) / 86400 ))
if [ "$days_since_last" -le 33 ]; then
  ok "last commit was $days_since_last days ago (artifact saw ~3d)"
else
  miss "last commit was $days_since_last days ago β€” artifact may be stale"
fi

echo
if [ "$fail" -eq 0 ]; then
  echo "artifact verified (0 failures) β€” safe to trust"
else
  echo "artifact has $fail stale claim(s) β€” regenerate at https://repopilot.app/r/timothymiller/cloudflare-ddns"
  exit 1
fi

Each check prints ok: or FAIL:. The script exits non-zero if anything failed, so it composes cleanly into agent loops (./verify.sh || regenerate-and-retry).

</details>

⚑TL;DR

A minimal Rust-based dynamic DNS (DDNS) client for Cloudflare that automatically detects IP changes and updates DNS records without requiring a static IP address. It's distributed as a ~1.1 MB static Docker image and supports IPv4/IPv6 dual-stack, multiple domains/zones, WAF list management, and integrates with notification services like Discord and Slack. Modular Rust binary: src/main.rs orchestrates the app, src/config.rs handles environment variable parsing, src/provider.rs abstracts IP detection (Cloudflare Trace, DoH, ipify, local interface), src/updater.rs manages DNS record updates, src/cloudflare.rs wraps the API, src/notifier.rs handles Shoutrrr integrations, src/domain.rs models DNS zones/records, and src/cf_ip_filter.rs filters invalid Cloudflare anycast IPs. Docker and systemd deployment files provide multiple runtime options.

πŸ‘₯Who it's for

Home lab operators, self-hosted infrastructure maintainers, and DevOps engineers who need to access internal networks via custom domain names from dynamic ISP connections, or anyone running Cloudflare-hosted DNS who wants a lightweight, memory-efficient DDNS solution (uses ~3.5 MB RAM at runtime).

🌱Maturity & risk

Production-ready and actively maintained. The project is at v2.1.2 with recent release notes (RELEASE_NOTES_2.1.2.md), has Docker CI/CD workflows (.github/workflows/image.yml), systemd integration, and Kubernetes manifests (k8s/cloudflare-ddns.yml). The high Docker pull count and static binary approach indicate solid adoption, though the maintainer appears to be primarily one person (timothymiller).

Low risk for core functionalityβ€”the dependency footprint is tightly controlled (reqwest, tokio, serde, regex-lite, url, if-addrs, rand only) and the release profile aggressively optimizes for size (LTO, single codegen unit, stripping). Primary risk is single-maintainer governance; check GitHub activity frequency and issue response times. No test directory visible in the file list is a gap for a DNS-critical tool.

Active areas of work

The project is stable with v2.1.2 released. Recent additions include WAF list management, heartbeat monitoring (Healthchecks.io, Uptime Kuma), cron scheduling, dry-run mode, graceful shutdown with signal handling, and managed record regex filtering. The dependabot.yml suggests active dependency maintenance. Check .github/workflows/image.yml for the current CI pipeline publishing multi-arch Docker images.

πŸš€Get running

Clone and build locally: git clone https://github.com/timothymiller/cloudflare-ddns.git && cd cloudflare-ddns && cargo build --release. Or run via Docker: docker run -d --name cloudflare-ddns --restart unless-stopped --network host -e CLOUDFLARE_API_TOKEN=xxx -e CLOUDFLARE_ZONE_ID=yyy -e CLOUDFLARE_RECORD_NAME=example.com timothyjmiller/cloudflare-ddns:latest. Configuration uses environment variables (see env-example) or config JSON (config-example.json).

Daily commands: Development: cargo run with env vars set (CLOUDFLARE_API_TOKEN, CLOUDFLARE_ZONE_ID, CLOUDFLARE_RECORD_NAME at minimumβ€”see env-example). Docker: docker-compose -f docker/docker-compose.yml up or use docker/docker-run.sh. Systemd: sudo systemctl enable cloudflare-ddns.timer && sudo systemctl start cloudflare-ddns.timer (requires systemd/cloudflare-ddns.service and systemd/cloudflare-ddns.timer installed). Cron interval is configurable via UPDATE_FREQUENCY env var (cron expression format).

πŸ—ΊοΈMap of the codebase

  • src/main.rs: Entry point orchestrating Tokio runtime, config loading, provider/updater initialization, signal handling, and main loop
  • src/config.rs: Parses all environment variables and optional JSON config file into Config struct; central source of truth for all user-configurable options
  • src/provider.rs: Abstracts IP detection strategy (Cloudflare Trace, DoH, ipify, local interface, custom URL, static); supports IPv4 and IPv6 independently
  • src/cloudflare.rs: Wraps Cloudflare REST API (authentication, list zones, read/update DNS records, manage WAF IP lists); all API errors bubble up here
  • src/updater.rs: Core logic comparing detected IP vs current DNS record and deciding whether to update Cloudflare; calls notifier on success/failure
  • src/cf_ip_filter.rs: Validates detected IP is not a Cloudflare anycast IP or invalid CGNAT/private range; prevents erroneous DNS updates
  • Cargo.toml: Defines aggressive release profile (opt-level=z, LTO, strip, panic=abort) achieving ~1.1 MB image; pin specific dependency versions
  • Dockerfile: Multi-stage build from scratch producing zero-dependency static binary; final image is ~1.1 MB
  • env-example: Documentation of all supported environment variables and their meanings; copy to .env for local dev
  • .github/workflows/image.yml: CI/CD pipeline building and pushing multi-architecture Docker images (amd64, arm64, etc.) on tag/commit

πŸ› οΈHow to make changes

New IP provider: Add variant to src/provider.rs and implement detection logic. New notification type: Extend src/notifier.rs (uses Shoutrrr under the hoodβ€”see NOTIFICATIONS_URL env var format). Cloudflare API feature: Add methods to src/cloudflare.rs struct and call from src/updater.rs. Domain/zone logic: Extend src/domain.rs. Config option: Add field to src/config.rs Config struct with environment variable binding. Testing: Add to src/ with #[cfg(test)] blocks (currently no dedicated tests/ directoryβ€”this is a gap). Docker build: Modify Dockerfile or scripts/docker-build.sh for image changes.

πŸͺ€Traps & gotchas

Required env vars: CLOUDFLARE_API_TOKEN and either CLOUDFLARE_ZONE_ID (for zones) or CLOUDFLARE_ACCOUNT_ID (for WAF lists) must be set or app will exit with error. IP provider defaults: If no IPv4_PROVIDER is set, defaults to Cloudflare Trace (cdn-cgi/trace); IPv6_PROVIDER defaults to Cloudflare DoH. Cron expression quirk: UPDATE_FREQUENCY uses cron syntax but Cloudflare Trace endpoint may rate-limit frequent checksβ€”recommend β‰₯5 minute intervals. WAF list danger: CLOUDFLARE_WAF_LIST_NAME will create/overwrite WAF lists; test in dry-run mode (DRY_RUN=true) first. Docker networking: Must use --network host or explicitly map ports if running outside Docker; Docker bridge may mask real IP. IDN handling: Domains with non-ASCII chars (e.g. mΓΌnchen.de) are auto-converted to punycode (xn--mnchen-3ya.de) by serde; transparent but may surprise users. Graceful shutdown: SIGTERM initiates cleanup (optionally deleting managed records via DELETE_ON_EXIT); ensure container stop timeout allows graceful shutdown. Single maintainer: Dependency updates via dependabot but review latency depends on timothymiller availability.

πŸ’‘Concepts to learn

  • Dynamic DNS (DDNS) β€” Core domain: understanding how DDNS solves the problem of accessing systems behind dynamic ISP IP assignments is foundational to this entire project
  • CGNAT (Carrier-Grade NAT) and IP Range Filtering β€” The project explicitly filters 100.64.0.0/10 and other shared address spaces in cf_ip_filter.rs; this prevents false updates when behind CGNAT, a hidden trap for ISP subscribers
  • Anycast IP Routing β€” Cloudflare uses anycast for its DNS servers and cdn-cgi endpoint; cloudflare-ddns must filter these anycast IPs to avoid incorrectly updating DNS with Cloudflare's IPs instead of user's real IP (see cf_ip_filter.rs)
  • Internationalized Domain Names (IDN) and Punycode β€” The project supports non-ASCII domains (e.g. mΓΌnchen.de); serde transparently converts to punycode (xn--mnchen-3ya.de) for API calls, a critical consideration for European/Asian deployments
  • Cron Expression Scheduling β€” UPDATE_FREQUENCY uses cron syntax (e.g. '0 */5 * * * *' for every 5 min), leveraging Tokio's timer; understanding cron prevents misconfiguration and rate-limit surprises
  • Async/Await and Tokio Runtime β€” The entire app is async (main.rs uses tokio::main macro); understanding Tokio's executor, channels, and signal handling is essential for modifying concurrency or adding long-running tasks
  • Static Binary Linking and Scratch Container Images β€” The Dockerfile builds a static Rust binary and deploys from-scratch (zero base image), a Docker best practice for minimal footprint; this requires rustls instead of OpenSSL and no runtime dependencies
  • curl/curl β€” While curl is a utility not a DDNS tool, it's the industry standard for HTTP requests in Bash-based DDNS scripts; cloudflare-ddns is the Rust/Docker-native replacement for shell-based curl loops
  • DNSroboCom/DNSrobo β€” Another DDNS client for Cloudflare written in Go, but heavier (~10+ MB image) and higher memory footprint; cloudflare-ddns achieves the same features in ~1.1 MB with static Rust
  • qdm12/ddns-updater β€” Multi-provider DDNS updater supporting Cloudflare, Namecheap, DigitalOcean, etc. in Go; cloudflare-ddns is Cloudflare-focused and significantly leaner
  • cloudflare/wrangler β€” Cloudflare's official CLI tool for Workers and Pages; users of cloudflare-ddns often interact with Cloudflare's API ecosystem and may use Wrangler for edge deployments alongside DDNS
  • shoutrrr/shoutrrr β€” The notification abstraction library cloudflare-ddns uses (integrated via Shoutrrr-compatible URLs); users extending notification support should study Shoutrrr's service implementations

πŸͺ„PR ideas

To work on one of these in Claude Code or Cursor, paste: Implement the "<title>" PR idea from CLAUDE.md, working through the checklist as the task list.

Add integration tests for IP provider detection (src/provider.rs)

The repo uses multiple IP detection providers but lacks integration tests. The src/provider.rs module handles critical functionality for fetching public IPs from different sources. Adding tests with wiremock (already in dev-dependencies) would prevent regressions and validate provider fallback behavior, especially important since IP detection failures would break the entire DDNS update cycle.

  • [ ] Create src/provider.rs unit tests for each provider (IPv4/IPv6 detection)
  • [ ] Add wiremock-based integration tests simulating provider API responses and failures
  • [ ] Test provider fallback logic when primary provider is unavailable
  • [ ] Validate parsing of IP responses from different providers
  • [ ] Add tests for if-addrs local IP detection in src/provider.rs

Add end-to-end Docker security scanning workflow in .github/workflows/

The repo distributes Docker images (evidenced by docker/, Dockerfile, and docker-publish.sh scripts) with the claim of being 'secure' but lacks automated security scanning. Add a GitHub Actions workflow to scan for vulnerabilities in the final image and Dockerfile, complementing the existing image.yml workflow. This is critical for a security-sensitive tool that manages DNS records.

  • [ ] Create .github/workflows/security-scan.yml with Trivy or similar container scanning
  • [ ] Scan both the final Docker image and base scratch image for vulnerabilities
  • [ ] Add SARIF output integration for GitHub Security tab visibility
  • [ ] Test that workflow runs on PR and main branch pushes
  • [ ] Document any secrets/credentials needed in SECURITY.md

Add comprehensive unit tests for src/config.rs and src/domain.rs validation

Configuration parsing and domain validation are critical path code that directly impacts user experience and Cloudflare API calls. The repo has tempfile in dev-dependencies but src/config.rs and src/domain.rs lack visible test coverage. Invalid configurations could silently fail or make incorrect API calls. Tests should validate the config-example.json structure, environment variable parsing, and domain name validation.

  • [ ] Add tests in src/config.rs for parsing config-example.json and environment variables
  • [ ] Test edge cases: missing API tokens, invalid domain names, malformed JSON
  • [ ] Add tests in src/domain.rs for domain validation and normalization logic
  • [ ] Use tempfile to test file-based configuration loading
  • [ ] Add tests for invalid/mixed credential sources to prevent misconfiguration

🌿Good first issues

  • Add integration tests to src/cloudflare.rs using wiremock (wiremock is already in dev-dependencies at Cargo.toml). Mock Cloudflare API responses for CRUD operations on DNS records and WAF lists, and validate error handling paths (invalid API tokens, rate limits, malformed responses). This closes a major quality gap for a DNS-critical tool.
  • Extend src/provider.rs with a new IP detection provider option: add support for retrieving IP from a local HTTP service (e.g. /ip endpoint on localhost:8080) via a LOCAL_HTTP_PROVIDER env var. This enables custom enterprise or edge-computing setups. Add to config.rs, update env-example, and document in README.
  • Add dry-run result summary output to src/updater.rs: when DRY_RUN=true, output a formatted table showing each domain, current DNS IP, detected IP, and whether an update would occur. Currently dry-run mode silently skips writes; this improves visibility for debugging and validation before running live.

⭐Top contributors

Click to expand

πŸ“Recent commits

Click to expand
  • fddabc7 β€” Release v2.1.2 (timothymiller)
  • 548d89d β€” Make Cloudflare lookups case-insensitive (timothymiller)
  • 22320be β€” Release v2.1.1 (timothymiller)
  • 1bb347b β€” Merge pull request #263 from DMaxter/master (timothymiller)
  • 1d5ad27 β€” Merge pull request #265 from timothymiller/dependabot/cargo/reqwest-0.13.3 (timothymiller)
  • 08ff76f β€” Merge pull request #266 from timothymiller/dependabot/cargo/rand-0.10.1 (timothymiller)
  • 199bbae β€” Merge pull request #267 from timothymiller/dependabot/cargo/rustls-0.23.40 (timothymiller)
  • 591f3e4 β€” Bump rustls from 0.23.39 to 0.23.40 (dependabot[bot])
  • 687d299 β€” docs: document the variable in the README (DMaxter)
  • 25122d2 β€” Bump rand from 0.9.3 to 0.10.1 (dependabot[bot])

πŸ”’Security observations

The Cloudflare DDNS project demonstrates strong security fundamentals with a minimalist Rust-based approach, static binary compilation, and proper use of security-focused features (LTO, stripping, panic=abort). The codebase avoids common pitfalls like SQL injection and XSS. However, security documentation is incomplete, and some build optimizations (UPX compression) may introduce unnecessary complexity. The use of TLS with rustls requires careful verification of provider configuration. Overall security posture is good with minor improvements recommended in documentation completeness and dependency management transparency.

  • Medium Β· Incomplete Security Policy Documentation β€” SECURITY.md. The SECURITY.md file is truncated and incomplete. The vulnerability reporting section ends abruptly at 'What to Include', missing critical information about response timelines, disclosure policies, and security contact details. Fix: Complete the SECURITY.md file with full vulnerability reporting guidelines, expected response times (e.g., 90-day disclosure deadline), and complete security contact information.
  • Low Β· Potential Information Disclosure via UPX Compression β€” Dockerfile, line: RUN apk add --no-cache upx && upx --best --lzma target/release/cloudflare-ddns. The Dockerfile uses UPX (Ultimate Packer for eXecutables) with aggressive compression settings (--best --lzma) on the release binary. UPX can introduce security-relevant side effects and may complicate security analysis. Additionally, UPX-compressed binaries can sometimes be reverse-engineered more easily than expected. Fix: Evaluate if the minimal size gain justifies the compression overhead. Consider removing UPX or using standard release compilation optimizations (already configured in Cargo.toml with lto=true, opt-level='z', strip=true).
  • Low Β· Missing Certificate Validation Context β€” Cargo.toml (reqwest dependency with rustls-no-provider). The application uses rustls-no-provider feature for reqwest, requiring manual provider configuration. While certificates are copied into the scratch container, there's no visible evidence of explicit certificate validation configuration in the Cargo.toml review, which could lead to HTTPS validation issues. Fix: Verify that the 'ring' provider (already included) is properly initialized in src/main.rs or relevant networking modules. Document the TLS provider setup explicitly.
  • Low Β· Weak Version Pinning in Dependencies β€” Cargo.toml. Several dependencies use loose version constraints (e.g., 'reqwest = 0.13', 'tokio = 1') without patch-level pinning. Cargo.lock exists and should mitigate this in production, but dependency version ranges could introduce unexpected behavior across environments. Fix: While Cargo.lock provides protection, consider documenting the dependency update strategy. Ensure Cargo.lock is always committed and used in CI/CD pipelines.

LLM-derived; treat as a starting point, not a security audit.


Generated by RepoPilot. Verdict based on maintenance signals β€” see the live page for receipts. Re-run on a new commit to refresh.

Mixed signals Β· timothymiller/cloudflare-ddns β€” RepoPilot