Skip to main content

Manage Cloudflare Rules as IaC

Project description

octorules

Cloudflare Rules as code - Manage rules across zones declaratively

In the vein of infrastructure as code, octorules provides tools & patterns to manage Cloudflare Rules (Redirect Rules, Cache Rules, Origin Rules, WAF Custom Rules, Rate Limiting, and more) as YAML files. The resulting config can live in a repository and be deployed just like the rest of your code, maintaining a clear history and using your existing review & workflow.

octodns manages DNS records, but can't touch Cloudflare's newer Rules products. octorules fills that gap — one YAML file per domain, plan-before-apply, fail-fast on errors.

Getting started

Installation

pip install octorules

Configuration

Create a config file pointing at your zones:

# config.yaml
providers:
  cloudflare:
    token: env/CLOUDFLARE_API_TOKEN
  rules:
    directory: ./rules

zones:
  example.com:
    sources:
      - rules

The env/ prefix resolves values from environment variables at runtime — keep secrets out of YAML.

YAML files support !include directives to split large configs:

zones:
  example.com: !include zones/example.yaml
# rules/example.com.yaml
redirect_rules: !include shared/redirects.yaml

Includes resolve relative to the file containing the directive. Nested includes and circular include detection are supported. Includes are confined to the directory tree of the parent file.

Defining rules

Create a rules file for each zone:

# rules/example.com.yaml
redirect_rules:
  - ref: blog-redirect
    description: "Redirect /blog to blog subdomain"
    expression: 'starts_with(http.request.uri.path, "/blog/")'
    action_parameters:
      from_value:
        target_url:
          expression: 'concat("https://blog.example.com", http.request.uri.path)'
        status_code: 301

cache_rules:
  - ref: cache-static-assets
    description: "Cache static assets for 24h"
    expression: 'http.request.uri.path.extension in {"jpg" "png" "css" "js"}'
    action_parameters:
      cache: true
      edge_ttl:
        mode: override_origin
        default: 86400

Each rule requires a ref (stable identifier, unique within a phase) and an expression (Cloudflare ruleset expression). Optional fields include description, enabled (defaults to true), action, and action_parameters.

Usage

# Preview changes (dry-run)
octorules plan --config config.yaml

# Apply changes
octorules sync --doit --config config.yaml

# Validate offline (no API calls, useful in CI)
octorules validate --config config.yaml

# Export existing rules to YAML
octorules dump --config config.yaml

Supported phases

YAML Key Cloudflare Phase Default Action
redirect_rules http_request_dynamic_redirect redirect
url_rewrite_rules http_request_transform rewrite
request_header_rules http_request_late_transform rewrite
response_header_rules http_response_headers_transform rewrite
config_rules http_config_settings set_config
origin_rules http_request_origin route
cache_rules http_request_cache_settings set_cache_settings
compression_rules http_response_compression compress_response
custom_error_rules http_custom_errors serve_error
waf_custom_rules http_request_firewall_custom (must specify)
waf_managed_exceptions http_request_firewall_managed (must specify)
rate_limiting_rules http_ratelimit (must specify)

Phases with a default action don't need action in the YAML — it's injected automatically. For waf_custom_rules, waf_managed_exceptions, and rate_limiting_rules, you must specify action explicitly (e.g., block, challenge, log).

Only custom_error_rules, waf_custom_rules, waf_managed_exceptions, and rate_limiting_rules are supported at account level — zone-only phases are automatically skipped for account scopes.

CLI reference

octorules plan

Dry-run: shows what would change without touching Cloudflare. Exit code 2 when changes are detected. Output format and destination are controlled via manager.plan_outputs in the config file (defaults to text on stdout).

octorules plan [--zone example.com] [--phase redirect_rules] [--checksum]

octorules sync --doit

Applies changes to Cloudflare. Requires --doit as a safety flag. Atomic PUT per phase, fail-fast on errors.

octorules sync --doit [--zone example.com] [--phase redirect_rules] [--checksum HASH] [--force]

octorules compare

Compare local rules against live Cloudflare state. Exit code 1 when differences exist.

octorules compare [--zone example.com] [--checksum]

octorules report

Drift report showing deployed vs YAML source of truth.

octorules report [--zone example.com] [--output-format csv|json]

octorules validate

Validates config and rules files offline (no API calls). Useful in CI to catch errors early.

octorules validate [--zone example.com] [--phase redirect_rules]

octorules dump

Exports existing Cloudflare rules to YAML files. Useful for bootstrapping or importing an existing setup.

octorules dump [--zone example.com] [--output-dir ./rules]

Common flags

Flag Description
--config PATH Path to config file (default: config.yaml)
--zone NAME Process a single zone (default: all)
--phase NAME Limit to specific phase(s); can be repeated
--debug Enable debug logging
--quiet Only show errors

Exit codes

Code Meaning
0 Success / no changes
1 Error
2 Changes detected (plan)

Config reference

providers:
  cloudflare:
    token: env/CLOUDFLARE_API_TOKEN  # env/ prefix reads from environment
    max_retries: 2                   # API retry count (default: 2)
    timeout: 30                      # API timeout in seconds (optional)
    safety:
      delete_threshold: 30.0        # Max % of rules that can be deleted (default: 30)
      update_threshold: 30.0        # Max % of rules that can be updated (default: 30)
      min_existing: 3               # Min rules before thresholds apply (default: 3)
  rules:
    directory: ./rules               # Path to rules directory

manager:
  max_workers: 4                     # Parallel zone processing (default: 4)
  plan_outputs:                      # Config-driven plan output (replaces --format/--output)
    text:
      class: octorules.plan_output.PlanText
    html:
      class: octorules.plan_output.PlanHtml
      path: /tmp/plan.html           # Optional: write to file instead of stdout

zones:
  example.com:
    sources:
      - rules
    allow_unmanaged: false           # Keep rules not in YAML (default: false)
    always_dry_run: true             # Never apply changes (default: false)
    safety:                          # Per-zone overrides
      delete_threshold: 50.0

How it works

  1. Plan — Reads your YAML rules, fetches current rules from Cloudflare, computes a diff by matching rules on ref.
  2. Sync — Executes the plan by doing an atomic PUT per phase (full replacement of the phase ruleset). Fail-fast on errors.
  3. Dump — Fetches all rules from Cloudflare and writes them to YAML files, stripping API-only fields (id, version, last_updated, etc.).

Performance:

  • Parallel phase fetching — phases within each scope are fetched concurrently (up to 4 workers).
  • Parallel zone processing — multiple zones are planned/synced concurrently (configurable via max_workers, default: 4).
  • Parallel zone ID resolution — zone name lookups run concurrently.
  • Concurrent account planning — account-level rules are planned in parallel with zone rules.
  • Account phase filtering — only account-compatible phases are fetched for account scopes, eliminating wasted API calls.

Safety features:

  • --doit flag — sync requires explicit confirmation.
  • Delete thresholds — blocks mass deletions above a configurable percentage.
  • Checksum verificationplan --checksum produces a hash; sync --checksum HASH verifies the plan hasn't changed.
  • Auth error propagation — authentication and permission errors fail immediately instead of being silently swallowed.
  • Failed phase filtering — phases that can't be fetched are excluded from planning to prevent accidental mass deletions.
  • Path traversal protection!include directives and file operations are confined to their expected directories.

CI/CD integration

# .github/workflows/rules.yaml
jobs:
  validate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: pip install octorules
      - run: octorules validate

  plan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: pip install octorules
      - run: octorules plan --checksum

  deploy:
    if: github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: pip install octorules
      - run: octorules sync --doit

Development

Local setup

git clone git@github.com:doctena-org/octorules.git
cd octorules
python -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"

Running tests and linting

pytest
ruff check src/ tests/
ruff format --check src/ tests/

Releasing a new version

  1. Update the version in pyproject.toml (single source of truth).
  2. Commit and push to main.
  3. Tag the release and push the tag:
git tag v0.7.0
git push origin v0.7.0

Pushing a v* tag triggers the publish workflow, which builds the package, publishes it to PyPI, and creates a GitHub Release.

License

octorules is licensed under the Apache License 2.0.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

octorules-0.7.0.tar.gz (71.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

octorules-0.7.0-py3-none-any.whl (35.6 kB view details)

Uploaded Python 3

File details

Details for the file octorules-0.7.0.tar.gz.

File metadata

  • Download URL: octorules-0.7.0.tar.gz
  • Upload date:
  • Size: 71.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for octorules-0.7.0.tar.gz
Algorithm Hash digest
SHA256 22855f2bf0d941fcea6856fd5a08ede40f8f35610405d0320fa56258df336ca9
MD5 d91e21bae03174522421272345443944
BLAKE2b-256 823c920d3cb52acf4e84cf9bf61b401d3df62fdb45cad23ed54b4afc45832cbe

See more details on using hashes here.

Provenance

The following attestation bundles were made for octorules-0.7.0.tar.gz:

Publisher: publish.yaml on doctena-org/octorules

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file octorules-0.7.0-py3-none-any.whl.

File metadata

  • Download URL: octorules-0.7.0-py3-none-any.whl
  • Upload date:
  • Size: 35.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for octorules-0.7.0-py3-none-any.whl
Algorithm Hash digest
SHA256 40cc22b26aea14e07fe51144855dab94d060fe121c4151c78e15447869206940
MD5 3a0a096a18f12ede694efbd62e522b76
BLAKE2b-256 c6e6f6b9153fee3d64ec6b77d1ad7e4211c0b7d060798df732becbf44676aef4

See more details on using hashes here.

Provenance

The following attestation bundles were made for octorules-0.7.0-py3-none-any.whl:

Publisher: publish.yaml on doctena-org/octorules

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page