Skip to content

Architecture

See Database Architecture for the Phase 1 database workload, telemetry ingestion, reporting read-model, reconciliation, and retention foundation.

CDNLite is a self-hosted CDN control plane with a dashboard, API, PostgreSQL state store, OpenResty/Lua edge proxy, signed edge-agent loop, and PowerDNS/DNSGeo publishing. This page explains the major components and how private CDN traffic, DNS state, cache rules, WAF rules, and edge telemetry move through the system.

CDNLite is split into a control plane, dashboard, data-plane edge, and agent loop.

Private CDN Request Flow

mermaid
flowchart LR
  Browser[Browser Dashboard] --> Core[Core API]
  Core --> Postgres[(PostgreSQL)]
  Core --> PowerDNS[PowerDNS / DNSGeo]
  EdgeAgent[Edge Agent] --> Core
  EdgeProxy[OpenResty Edge Proxy] --> Origin[Origin Servers]
  EdgeProxy --> Events[Metrics / Security Events]
  Events --> Core

DNS Reconciler

Durable DNS publishing has one path. DnsDesiredStateBuilder projects Core state, DnsReconciler serializes runs with a PostgreSQL advisory lock, and PowerDnsService applies and verifies batched rrset changes. The persisted desired table is also the ownership ledger used to remove stale rrsets after records or domains are deleted.

The Compose dns-reconciler service runs the same command used by mutation-triggered and operator-triggered syncs. ACME DNS-01 TXT records are deliberately excluded because they are short-lived challenge state rather than durable product DNS state.

Scale-critical DNS lookups use partial indexes for active records and ownership indexes for desired generations and zones. ci/stress-dns.sh validates those plans and records full-sync, edge-only sync, API latency, changed rrsets, and changed customer-zone counts against the real root-Compose PowerDNS stack.

System Overview

text
Operator Browser
      |
      v
Vue Dashboard -----> Core PHP API -----> PostgreSQL
      |                    |
      |                    +----> DNSGeo PowerDNS API
      |                    |
      |                    +----> Config snapshots
      |
      v
OpenResty Edge <---- Edge Agent <---- signed config/heartbeat endpoints
      |
      v
Customer Origins

Both OpenResty listeners suppress version tokens and remove the Server response header. Lua-generated CDN error pages replace native branded 5xx responses.

Components

ComponentTechnologyResponsibility
Core APIPHP 8.3, custom routerDomain, DNS, rules, SSL, settings, analytics, admin auth, edge auth.
DatabasePostgreSQL 16Domains, records, rules, snapshots, usage, events, audits, admins.
DashboardVue 3, TypeScript, Vite, Pinia, TanStack Query, Tailwind, EChartsBrowser admin console.
Edge runtimeOpenResty, Nginx, LuaHost routing, caching, rule enforcement, TLS serving, metric queues.
Edge agentPOSIX shell, curl, OpenSSLRegister, heartbeat, pull config, push metrics, push security events.

The agent reports health_status=healthy with each successful signed heartbeat and treats non-success HTTP responses as heartbeat failures. Core combines that status with heartbeat freshness and the enabled flag when building the shared PowerDNS/DNSGeo edge pool. Heartbeat and registration requests update PostgreSQL state only; PowerDNS publication happens through normal reconcile and force-sync flows so agent traffic does not block on DNS network I/O. | CI and controlled services | Bash, Docker Compose | Smoke/e2e validation, origin services, real DNSGeo/PowerDNS. |

Core treats a PowerDNS PATCH as successful only after an optional zone read-back confirms the requested replacement or deletion. Retryable transport, rate-limit, and server failures use bounded exponential backoff; invalid 4xx requests are returned immediately. Each write updates dns_sync_state and appends dns_sync_events. /cdn-health and the PowerDNS doctor expose API and persisted sync health without revealing credentials.

Dashboard collection views share paginated table controls. Global security and audit APIs provide bounded limit/offset queries, and the domain Activity tab applies domain_id at the API boundary. Event Viewer consumes global bounded operations APIs instead of issuing one request per domain.

Edge country resolution is shared by proxy origin selection, cache keys, and usage reporting. The edge first trusts X-CDNLITE-Country or CF-IPCountry for controlled upstream integrations and tests, then falls back to the mounted MMDB. Resolved values are sent to core as client_country in usage metrics, and the visitor address is sent as client_ip for request activity diagnostics. Those fields feed per-domain Activity summaries and dashboard traffic reports.

Request Flow

text
Client request
  -> OpenResty listens on 8081 or 8443
  -> Lua router uses the worker-local validated config snapshot
  -> host/domain lookup chooses origin and rules
  -> redirects, WAF, rate limit, IP, cache, and headers are evaluated
  -> request proxies to a weighted primary, backup, or shield origin from the origin pool
  -> metrics/security events enter bounded shared queues
  -> edge agent later pushes queues to core

Origin routing is deterministic for a given request seed. The edge filters disabled, drained, and actively unhealthy checked origins, selects healthy primaries before backups, and uses weighted hashing inside the chosen pool. Origin snapshots also carry bounded idempotent retries, retry budgets, circuit breaker settings, connection limits, and shield metadata. Non-idempotent methods do not receive automatic retry attempts by default.

Origin health is edge-observed. Core keeps manual diagnostics for troubleshooting, but scheduled core probes do not drive routing health. Edge workers actively probe user-enabled monitored origins and also emit passive edge-origin health observations from proxied requests. The edge agent pushes those metrics to core, where latency, jitter, slow-origin, timeout, TLS, connect, and HTTP failure details are stored by domain, origin, and edge node.

Config Flow

text
Admin/API change
  -> Core validates and writes PostgreSQL
  -> ConfigService marks the published edge config dirty
  -> One publisher rebuilds the materialized snapshot under a PostgreSQL advisory lock
  -> Active snapshot stays valid until the new version is stored
  -> Edge agent signs GET /api/v1/edge/config
  -> Agent writes config.json atomically
  -> OpenResty workers validate and atomically activate the fresh snapshot

OpenResty keeps a parsed last-known-good snapshot per worker. Workers reload only when file metadata changes or a local operator calls /__cdnlite_reload_config from inside the edge container. Malformed, oversized, or unsupported snapshots increment reload failure counters and do not replace a healthy active snapshot. /ready exposes the active config version, checksum, load time, reload counters, last reload error, stale age warning, and telemetry queue health.

Data Flow

DataProducerConsumer
Domain and rule stateDashboard, API, CLICore services and config snapshot builder.
Config snapshot JSONCore ConfigServiceEdge agent and OpenResty runtime.
Metrics NDJSONOpenResty bounded queue flusherEdge agent, collector API, usage aggregates.
Security events NDJSONOpenResty bounded queue flusherEdge agent, collector API, dashboard. Concurrent push attempts are serialized with a queue-scoped lock.
Origin healthOpenResty and edge agentReadiness service, edge backup routing config, and dashboard reports.
Origin resilience policyOrigins APIConfig snapshot builder and OpenResty selector.
edge-origin health observationsOpenResty and edge agentCollector, origin health reports, and dashboard detail.
Audit recordsCore servicesAudit log dashboard and API.

Deployment Topology

The root docker-compose.yml is the supported local and CI stack:

text
postgres
core
ssl-scheduler
origin-health-scheduler
edge
edge-agent
dashboard
origin-http
origin-tls
pdns-postgres -> pdns-db-init -> pdns-auth
pdns-mmdb-updater ------------^
pdns-recursor ----------------^
poweradmin -------------------^

CI intentionally uses this root Compose file. Do not add CI-only override files; use environment variables for job-specific behavior.

Storage

PostgreSQL is the supported backend. The edge uses mounted local files under /var/lib/cdnlite for config, sync status, metrics queues, and security-event queues. Logs are written under OpenResty log paths and surfaced through Compose logs.

Security Boundaries

  • Browser admin sessions are short-lived bearer tokens from /api/v1/admin/login.
  • API token auth protects control-plane endpoints when CDNLITE_API_TOKEN is configured.
  • Edge endpoints require edge ID, bearer token, timestamp, nonce, and HMAC signature.
  • SSL material depends on CDNLITE_SSL_SECRET_KEY; keep it stable and private.
  • Dashboard Vite variables are compiled into browser assets and must not contain production secrets unless the deployment is private and separately protected. The separate nameserver-scheduler periodically resolves every customer domain's NS set. Verification controls domain lifecycle: matching delegation enables desired-active records, while missing or changed delegation disables their effective publication without overwriting each record's desired status. Operators can also run an immediate per-domain verification from the dashboard or POST /api/v1/domains/{domainId}/nameservers/verify, which returns the expected, observed, matched, and missing nameserver sets. Admin sessions can use POST /api/v1/domains/{domainId}/nameservers/force-verify with a reason when delegation cannot be observed but the domain must be activated; the action is audited, marks the edge config dirty, and triggers DNS reconciliation.

Released under the MIT License.