CDNLite Examples
These examples are copy-friendly starting points. Replace IDs, domains, tokens, and IPs with your own values.
First Domain
Create a domain through the API:
API Domain Workflow
API=http://localhost:8080
TOKEN=replace-with-token
curl -s -X POST "$API/api/v1/domains" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"Example","domain":"example.test"}'DOMAIN_ID=replace-with-domain-id
curl -s -X POST "$API/api/v1/domains/$DOMAIN_ID/dns/records" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"type":"A","name":"www","content":"203.0.113.10","ttl":300,"proxied":true}'curl -s -X POST "$API/api/v1/domains/$DOMAIN_ID/origins" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"host":"origin.example.test","scheme":"http","port":80,"role":"primary","enabled":true}'CLI Domain Workflow
docker compose exec core php artisan cdn:domain:create \
--domain=example.test \
--name=Example
docker compose exec core php artisan cdn:domain:list
docker compose exec core php artisan cdn:domain:verify-ns --domain_id="$DOMAIN_ID"
docker compose exec core php artisan cdn:domain:activate --domain_id="$DOMAIN_ID" --override=1First Cache Rule
curl -s -X PUT "$API/api/v1/domains/$DOMAIN_ID/cache/settings" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"default_ttl":"60s","query_string_mode":"ignore"}'curl -s -X POST "$API/api/v1/domains/$DOMAIN_ID/cache/purge" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"scope":"prefix","value":"https://example.test/assets/"}'Redirect Workflow
curl -s -X POST "$API/api/v1/domains/$DOMAIN_ID/redirects" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"source_path":"/old","target_url":"https://example.test/new","status_code":301,"enabled":true}'WAF Example
curl -s -X POST "$API/api/v1/domains/$DOMAIN_ID/waf-rules" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"Block admin probes","match_type":"path_contains","pattern":"/wp-admin","action":"block","enabled":true}'Header Preset Example
curl -s -X POST "$API/api/v1/domains/$DOMAIN_ID/headers" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"HSTS","header_name":"Strict-Transport-Security","header_value":"max-age=31536000; includeSubDomains","enabled":true}'Edge Token Workflow
docker compose exec core php artisan cdn:edge:register-token \
--edge_id=edge-local-1 \
--token=edge-dev-tokenPowerDNS Workflow
docker compose up -d --build
docker compose exec core php artisan cdn:powerdns:doctor
docker compose exec core php artisan cdn:powerdns:dry-run
docker compose exec core php artisan cdn:powerdns:force-sync
./ci/powerdns_dns_checks.shAnalytics Recalculation
curl -s -X POST "$API/api/v1/usage/recalculate" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"bucket":"hour"}'Retention Dry Run
docker compose exec core php artisan cdn:usage:prune --all --dry-run
docker compose exec core php artisan cdn:usage:prune --allEnable the retention-scheduler only after the dry-run counts match your policy:
CDNLITE_RETENTION_PRUNE_ENABLED=trueVerify And Activate A Lab Domain
curl -s -X POST "$API/api/v1/domains/$DOMAIN_ID/verify-nameservers" \
-H "Authorization: Bearer $TOKEN"
curl -s -X POST "$API/api/v1/domains/$DOMAIN_ID/activate" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"override":true}'Local lab tip: override:true is useful when you are testing fake domains such as example.test. Do not use it as the normal production activation path.
DNS Migration Example
Use this when moving a real website into CDNLite without breaking mail or verification records.
# HTTP traffic through the CDN.
curl -s -X POST "$API/api/v1/domains/$DOMAIN_ID/dns/records" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"type":"A","name":"@","content":"203.0.113.10","ttl":120,"proxied":true}'
curl -s -X POST "$API/api/v1/domains/$DOMAIN_ID/dns/records" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"type":"CNAME","name":"www","content":"example.com","ttl":120,"proxied":true}'
# Mail and verification stay DNS-only.
curl -s -X POST "$API/api/v1/domains/$DOMAIN_ID/dns/records" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"type":"TXT","name":"@","content":"v=spf1 include:_spf.example.net ~all","ttl":300,"proxied":false}'The apex input keeps its origin type, but public DNS publishes direct answers from the canonical edge pool: static anycast A/AAAA when configured, otherwise PowerDNS LUA A/AAAA. The proxied www record publishes CNAME to the stable site target.
Cutover checklist:
- Lower TTL before migration.
- Create records in CDNLite.
- Verify nameserver delegation.
- Activate the domain.
- Send test traffic with a
Hostheader. - Watch edge logs and analytics before raising TTL.
Test Edge Routing With Host Header
curl -i http://localhost:8081/ \
-H 'Host: example.test'Expected headers may include:
X-CDNLITE-Cache: MISS
X-CDNLITE-Origin: primaryRepeat the request to check cache behavior. A second request may become HIT when the response is cacheable and no bypass headers are present.
Cache Debugging Example
curl -i http://localhost:8081/assets/app.css \
-H 'Host: example.test'
curl -i http://localhost:8081/assets/app.css \
-H 'Host: example.test' \
-H 'Cache-Control: no-cache'If the no-cache request bypasses cache but a normal request does not, the edge is behaving as expected.
Redirect Test Example
curl -s -X POST "$API/api/v1/domains/$DOMAIN_ID/redirects/test" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"path":"/old","scheme":"https","host":"example.test"}'Safer WAF Rollout
Start in observe mode when the backend supports logging actions:
curl -s -X POST "$API/api/v1/domains/$DOMAIN_ID/waf-rules" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"Observe admin probes","match_type":"path_contains","pattern":"/wp-admin","action":"log","enabled":true}'Switch from log to block only after the security event stream shows the rule is not catching legitimate traffic.
Security Header Set
curl -s -X POST "$API/api/v1/domains/$DOMAIN_ID/headers" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"Frame protection","header_name":"X-Frame-Options","header_value":"DENY","enabled":true}'
curl -s -X POST "$API/api/v1/domains/$DOMAIN_ID/headers" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"MIME sniffing protection","header_name":"X-Content-Type-Options","header_value":"nosniff","enabled":true}'IP Access Example
Block a noisy subnet:
curl -s -X POST "$API/api/v1/domains/$DOMAIN_ID/ip-rules" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"Block noisy subnet","cidr":"198.51.100.0/24","action":"block","enabled":true}'Allow a private monitoring subnet:
curl -s -X POST "$API/api/v1/domains/$DOMAIN_ID/ip-rules" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"Allow monitors","cidr":"203.0.113.0/28","action":"allow","enabled":true}'Rate Limit Example
curl -s -X POST "$API/api/v1/domains/$DOMAIN_ID/rate-limits" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"Login protection","path":"/login","threshold":60,"window_seconds":60,"action":"block","enabled":true}'Start with a generous threshold, then tune from security events and application logs.
Issue SSL
Request an ACME DNS-01 certificate after DNS is configured:
docker compose exec core php artisan cdn:ssl:request --domain_id="$DOMAIN_ID"For production, confirm DNS delegation and ACME credentials before issuing certificates.
Register An Edge Node
Register or rotate an edge token, then verify the agent scripts parse cleanly:
docker compose exec core php artisan cdn:edge:register-token \
--edge_id=edge-local-1 \
--token=edge-dev-token
sh -n edge/agent/register.sh
sh -n edge/agent/heartbeat.sh
sh -n edge/agent/pull_config.sh
sh -n edge/agent/push_metrics.shRun Readiness Checks
docker compose config --quiet
docker compose exec core php artisan cdn:readiness:check
docker compose exec core php artisan cdn:powerdns:doctor
docker compose exec core php artisan cdn:edge:listSplit Deployment
Generate split deployment bundles for separate core, DNSGeo, and edge hosts:
REGISTRY_OWNER=example \
IMAGE_TAG=sha-0123456789abcdef \
DNS_BASE_DOMAIN=cdn.example.com \
CORE_PUBLIC_IP=192.0.2.10 \
DNSGEO_PUBLIC_IP=192.0.2.11 \
EDGE_PUBLIC_IP=198.51.100.20 \
deploy/generate-split-deployment.sh --auto --output ./generatedReview Deployment and Production Hardening before using split bundles outside a lab.
Next Steps
SSL Request Example
JOB_ID="$(
curl -s -X POST "$API/api/v1/domains/$DOMAIN_ID/ssl/request" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"hostnames":["example.test","*.example.test"]}' \
| jq -r '.data.job_id'
)"
curl -s "$API/api/v1/domains/$DOMAIN_ID/ssl/jobs/$JOB_ID" \
-H "Authorization: Bearer $TOKEN" \
| jq
curl -s "$API/api/v1/domains/$DOMAIN_ID/ssl/acme-status" \
-H "Authorization: Bearer $TOKEN"Use staging ACME first. The request endpoint queues a scheduler job; keep ssl-scheduler running and poll the job until it reaches issued or failed.
Signed Edge Config Pull Sketch
EDGE_ID=edge-local-1
EDGE_TOKEN=edge-dev-token
METHOD=GET
PATH_ONLY=/api/v1/edge/config
BODY_HASH=$(printf '' | openssl dgst -sha256 -binary | xxd -p -c 256)
TS=$(date +%s)
NONCE=$(openssl rand -hex 16)
CANONICAL=$(printf "%s\n%s\n%s\n%s\n%s" "$METHOD" "$PATH_ONLY" "$TS" "$NONCE" "$BODY_HASH")
KEY=$(printf '%s' "$EDGE_TOKEN" | openssl dgst -sha256 -binary | xxd -p -c 256)
SIG=$(printf '%s' "$CANONICAL" | openssl dgst -sha256 -mac HMAC -macopt "hexkey:$KEY" -binary | xxd -p -c 256)
curl -s "$API$PATH_ONLY" \
-H "Authorization: Bearer $EDGE_TOKEN" \
-H "X-CDNLITE-Edge-Id: $EDGE_ID" \
-H "X-CDNLITE-Timestamp: $TS" \
-H "X-CDNLITE-Nonce: $NONCE" \
-H "X-CDNLITE-Signature: $SIG"The edge agent scripts already do this; the example is for developers writing custom edge tooling.
Config Snapshot Publish And Prune
curl -s "$API/api/v1/config/snapshots" \
-H "Authorization: Bearer $TOKEN"
curl -s "$API/api/v1/config/snapshots/latest" \
-H "Authorization: Bearer $TOKEN"
curl -s -X POST "$API/api/v1/config/snapshots/rebuild" \
-H "Authorization: Bearer $TOKEN"
docker compose exec core php artisan cdn:config-snapshots:prune --keep=2 --batch=5000 --dry-runSnapshot payload history, diff, and rollback endpoints are disabled unless CDNLITE_CONFIG_SNAPSHOT_HISTORY_ENABLED=true; snapshots are a generated edge cache, while database tables remain the source of truth.
Recommendations
Generate proactive recommendations after traffic has reached a domain:
php artisan cdn:recommendations:generate --domain_id="$DOMAIN_ID"
curl -s "$API/api/v1/domains/$DOMAIN_ID/recommendations" \
-H "Authorization: Bearer $TOKEN"Each recommendation includes the reason it appeared and, when safe, a one-click action endpoint for applying the suggested protection, cache, or diagnostic step.
OpenAPI Client Generation
The OpenAPI document source is available at OpenAPI YAML.
Example with openapi-generator-cli:
openapi-generator-cli generate \
-i docs/public/api/openapi.yaml \
-g typescript-fetch \
-o /tmp/cdnlite-clientGenerated clients are useful for control-plane endpoints. Keep edge HMAC signing as a custom helper so you can hash the exact raw body that will be sent. Verification activates the domain automatically. Records created before this step remain saved and begin publishing once delegation is verified.
