GitOps for CDN Infrastructure: Version-Controlled Edge Configuration
CDN configurations are infrastructure. They control how millions of requests route, cache, and transform at the edge every second. Yet many teams still manage CDN settings through web dashboards — clicking through forms, hoping the person who made the last change documented it somewhere. GitOps brings the same discipline to CDN infrastructure that modern teams already apply to application code: every change is a commit, every deployment is a merge, and the live state always matches what's in the repository. This article covers how to implement GitOps workflows for CDN infrastructure using declarative configuration, pull-based reconciliation, and automated drift detection.
The Problem with Dashboard-Driven CDN Management
CDN providers offer polished dashboards for managing edge configuration. They're convenient for exploring features and making quick changes. They're also a liability in production. Dashboard changes leave no audit trail beyond the provider's own logs (which may not capture who changed what). They can't be peer-reviewed before deployment. They can't be tested in a staging environment. And when something breaks at 3 AM, the only way to understand what changed is to compare the current state against someone's memory of what it used to be.
Consider a typical incident: a cache rule change deployed through the dashboard causes origin overload because the new rule bypasses caching for a high-traffic path. Without version control, answering "what changed?" requires digging through the CDN provider's audit logs, correlating timestamps, and manually reconstructing the previous configuration. With GitOps, you run git log and see exactly what changed, who approved it, and when — then git revert to restore the previous state.
GitOps Principles Applied to CDN
GitOps is built on four principles, each mapping directly to CDN infrastructure management:
| Metric | Edge Processing | Origin Processing | Improvement |
|---|---|---|---|
| Latency (p50) | 8–15 ms | 120–300 ms | 15–20x faster |
| Cache Hit Ratio | 85–95% | N/A | Reduces origin load |
| TLS Handshake | 1–5 ms (nearby PoP) | 50–150 ms | 10–30x faster |
| Bandwidth Cost | $0.01–0.04/GB | $0.05–0.12/GB | 3–5x cheaper |
| Availability | 99.99% (multi-PoP) | 99.9% (single DC) | 10x fewer outages |
Declarative configuration. CDN state is described as data — YAML, HCL, JSON — not as a sequence of API calls. The configuration declares what the edge should look like, not the steps to get there. This means the configuration is idempotent: applying it multiple times produces the same result, regardless of the current state.
Version controlled. All configuration lives in a Git repository. Every change is a commit with an author, timestamp, and message. Feature branches and pull requests provide a natural workflow for proposing, reviewing, and approving changes before they reach production.
Automatically applied. Approved changes are applied to the live CDN by an automated agent — not by a human running commands or clicking buttons. This eliminates the gap between "approved" and "deployed" that causes configuration drift.
Continuously reconciled. A reconciliation loop compares the desired state in Git against the actual state at the CDN provider. If they diverge — from manual dashboard changes, partial API failures, or provider-side issues — the reconciler corrects the live state to match Git.
Repository Structure
Organize your CDN configuration repository around environments and concerns. Separate base configurations from environment-specific overrides, and isolate different types of edge logic into dedicated directories.
cdn-infrastructure/
├── environments/
│ ├── production/
│ │ ├── main.tf
│ │ ├── variables.tf
│ │ └── terraform.tfvars
│ ├── staging/
│ │ ├── main.tf
│ │ ├── variables.tf
│ │ └── terraform.tfvars
│ └── development/
│ └── ...
├── modules/
│ ├── origin-pool/
│ │ ├── main.tf
│ │ ├── variables.tf
│ │ └── outputs.tf
│ ├── cache-rules/
│ │ └── ...
│ ├── waf-rules/
│ │ └── ...
│ └── edge-functions/
│ └── ...
├── edge-functions/
│ ├── src/
│ │ ├── routing.js
│ │ ├── auth.js
│ │ └── transforms.js
│ ├── tests/
│ │ └── routing.test.js
│ └── wrangler.toml
├── policies/
│ ├── cache-policies.yaml
│ ├── security-headers.yaml
│ └── rate-limits.yaml
└── .github/
└── workflows/
├── plan.yaml
└── apply.yaml
This structure separates infrastructure definitions (Terraform modules) from business logic (edge functions) and policy declarations (YAML configs). Each can evolve independently, reviewed by different teams with different expertise.
Pull Request Workflow
Every CDN change follows the same workflow: branch, change, plan, review, merge, apply. The critical addition compared to standard CI/CD is the plan step — before any change reaches production, the automation generates a diff showing exactly what will change at the CDN provider.
# .github/workflows/plan.yaml
name: CDN Config Plan
on:
pull_request:
paths:
- 'environments/**'
- 'modules/**'
- 'policies/**'
jobs:
plan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Terraform
uses: hashicorp/setup-terraform@v3
- name: Terraform Init
working-directory: environments/production
run: terraform init
- name: Terraform Plan
working-directory: environments/production
run: terraform plan -no-color -out=plan.tfplan
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CF_API_TOKEN }}
- name: Post Plan to PR
uses: actions/github-script@v7
with:
script: |
const plan = require('fs').readFileSync(
'environments/production/plan.stdout', 'utf8'
);
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: `## CDN Configuration Plan\n\`\`\`\n${plan}\n\`\`\``
});
The plan output shows reviewers exactly which CDN resources will be created, modified, or destroyed. Reviewers can assess the blast radius before approving. For security-sensitive changes like WAF rule modifications, require additional approvals from the security team.
Drift Detection and Remediation
Drift occurs when the live CDN state diverges from Git — typically because someone made a manual change through the dashboard or API. Without drift detection, these undocumented changes accumulate until the next GitOps apply overwrites them, potentially breaking production.
# drift-detector.py
import subprocess
import json
import requests
def detect_drift():
result = subprocess.run(
["terraform", "plan", "-detailed-exitcode", "-json"],
capture_output=True, text=True,
cwd="environments/production"
)
# Exit code 2 = changes detected (drift)
if result.returncode == 2:
changes = parse_plan_output(result.stdout)
return {
"drifted": True,
"resources": changes,
"timestamp": datetime.utcnow().isoformat()
}
return {"drifted": False}
def handle_drift(drift_report):
if not drift_report["drifted"]:
return
for resource in drift_report["resources"]:
if resource["action"] == "update":
# Non-destructive drift: auto-remediate
if is_safe_to_auto_fix(resource):
apply_fix(resource)
notify_team(f"Auto-fixed drift: {resource['address']}")
else:
# Destructive or ambiguous: alert for manual review
create_drift_issue(resource)
notify_team(
f"DRIFT DETECTED: {resource['address']} "
f"requires manual review"
)
def is_safe_to_auto_fix(resource):
safe_resources = [
"cloudflare_page_rule",
"cloudflare_rate_limit",
"cloudflare_filter",
]
return resource["type"] in safe_resources
Run drift detection on a schedule — every 5-15 minutes for critical CDN configurations, hourly for less sensitive settings. The key decision is the remediation policy: which drift is safe to auto-fix (restoring Git's desired state) versus which requires human review (the manual change may have been an emergency fix that should be committed to Git instead).
Multi-CDN GitOps
For organizations using multiple CDN providers, GitOps provides a unified management layer. Define an abstraction format that captures intent without provider-specific syntax, then use provider adapters to translate the abstract configuration into each provider's native format.
# policies/cache-policies.yaml (provider-neutral)
cache_rules:
- name: static-assets
match:
path_patterns:
- "*.css"
- "*.js"
- "*.woff2"
- "*.svg"
behavior:
cache_ttl: 31536000 # 1 year
browser_ttl: 86400 # 1 day
stale_while_revalidate: 3600
stale_if_error: 86400
- name: api-responses
match:
path_prefix: "/api/"
methods: ["GET"]
behavior:
cache_ttl: 60
vary_on:
- "Accept"
- "Authorization"
bypass_on:
- cookie: "session_*"
- name: html-pages
match:
content_type: "text/html"
behavior:
cache_ttl: 300
stale_while_revalidate: 60
cache_key_includes:
- query_string
- accept_encoding
The adapter layer translates this into Cloudflare Page Rules, Fastly VCL snippets, or CloudFront cache behaviors. When you change a policy in the YAML, all providers are updated consistently through their respective reconciliation loops.
Edge Function Versioning
Edge functions — Cloudflare Workers, Lambda@Edge, Fastly Compute — are code, and they deserve the same GitOps treatment as infrastructure configuration. Version edge functions alongside CDN configuration, test them in CI, and deploy them through the same pull request workflow.
# wrangler.toml (Cloudflare Workers configuration)
name = "edge-router"
main = "src/routing.js"
compatibility_date = "2026-09-01"
[env.staging]
name = "edge-router-staging"
routes = [
{ pattern = "staging.example.com/*", zone_name = "example.com" }
]
[env.production]
name = "edge-router-production"
routes = [
{ pattern = "example.com/*", zone_name = "example.com" },
{ pattern = "www.example.com/*", zone_name = "example.com" }
]
[build]
command = "npm run build"
[observability]
enabled = true
Pin the compatibility_date to prevent the Workers runtime from changing behavior under your code. Update it deliberately when you're ready to adopt new platform features, and test against the new date in staging before promoting to production.
Rollback Strategies
GitOps makes rollback trivial in theory — revert the commit and let the reconciler apply the previous state. In practice, CDN rollbacks have nuances that require careful handling.
Configuration rollbacks are straightforward. Revert the Terraform state to a previous version and apply. The reconciler detects the difference between the live state and the reverted desired state, then applies the changes. For CDN configurations that propagate across edge PoPs, expect a 5-30 second window where some PoPs serve the new configuration while others still serve the old one.
Edge function rollbacks are trickier because function deployments may involve compiled artifacts. Keep previous deployment artifacts in a registry or artifact store so that reverting the Git commit also restores the previous function binary, not just the source code. Most CDN providers maintain previous deployments internally and support instant rollback through their API.
Integrate rollback automation with your monitoring system. If key metrics degrade after a GitOps apply, automatically revert the last commit and re-apply the previous state. This creates a fully automated deploy-monitor-rollback loop that responds faster than any human operator.
Security Considerations
- Branch protection — Require pull request reviews for all CDN configuration changes. For WAF rule changes, require security team approval.
- Secret management — Never store API tokens, origin credentials, or signing keys in the Git repository. Use external secret managers with dynamic credential rotation. Reference secrets by name in configuration files; resolve them at apply time.
- Least privilege — The reconciliation agent's API token should have only the permissions needed for the resources it manages. Separate tokens for different resource types limit the blast radius of a compromised credential.
- Audit logging — Git provides change history. Supplement it with CDN provider audit logs and reconciler execution logs. Store all three in a centralized logging system for incident investigation.
- Change rate limits — Limit how many resources the reconciler can modify per cycle to prevent cascading failures from a misconfigured commit.
Implementation Checklist
| Phase | Action | Tool |
|---|---|---|
| Foundation | Import existing CDN config into Terraform state | terraform import |
| Foundation | Set up repository structure with modules | Git + Terraform |
| Workflow | Configure PR-based plan-and-apply pipeline | GitHub Actions / Atlantis |
| Workflow | Add staging environment for pre-production testing | Terraform workspaces |
| Safety | Implement drift detection on a schedule | Cron + terraform plan |
| Safety | Define auto-remediation policies | Custom reconciler |
| Advanced | Add edge function CI/CD pipeline | Wrangler / provider CLI |
| Advanced | Implement automated rollback triggers | Monitoring + reconciler |