Bunny.net DNS & Pull Zone Auto-Setup
A Python CLI tool for declaratively managing DNS zones, Pull Zones, and Edge Rules on bunny.net from a JSON configuration file.
Features
- Declarative configuration - Define your desired state in JSON, the tool syncs it to bunny.net
- DNS management - Create zones, manage A, AAAA, CNAME, TXT, MX, SRV, and other record types
- Pull Zone management - Create CDN pull zones with custom origins and regional pricing
- Hostname & SSL - Automatically add custom hostnames and provision free SSL certificates
- Edge Rules - Configure CDN edge rules with friendly action/trigger names
- Pull from bunny.net - Export existing infrastructure as a config JSON (
--sot bunny) - Dry-run mode - Preview changes before applying
- Domain isolation - Sync specific domains without affecting others
Installation
uv pip install bunny-dns-sync
Configuration
- Copy the example files:
cp .env.example .env
cp config.example.json config.json
- Add your bunny.net API key to
.env:
BUNNY_API_KEY=your-api-key-here
- Edit
config.jsonwith your domains and records.
Configuration Format
{
"domains": {
"example.com": {
"dns_records": [
{"type": "A", "name": "@", "value": "1.2.3.4", "ttl": 3600},
{"type": "AAAA", "name": "@", "value": "2606:50c0:8000::153", "ttl": 3600},
{"type": "CNAME", "name": "www", "value": "example.com", "ttl": 3600},
{"type": "MX", "name": "@", "value": "mail.example.com", "priority": 10, "ttl": 3600},
{"type": "TXT", "name": "@", "value": "v=spf1 include:_spf.google.com ~all", "ttl": 3600},
{"type": "SRV", "name": "_sip._tcp", "value": "sip.example.com", "priority": 10, "weight": 60, "port": 5060, "ttl": 3600}
],
"pull_zones": {
"my-cdn": {
"origin_url": "https://origin.example.com",
"origin_host_header": "origin.example.com",
"type": "standard",
"enabled_regions": ["EU", "US"],
"hostnames": ["cdn.example.com"],
"edge_rules": []
}
}
}
}
}
Supported DNS Record Types
| Type | Description |
|---|---|
| A | IPv4 address |
| AAAA | IPv6 address |
| CNAME | Canonical name |
| TXT | Text record |
| MX | Mail exchange (requires priority) |
| SRV | Service record (requires priority, weight, port) |
| CAA | Certificate Authority Authorization |
| NS | Name server |
| PTR | Pointer record |
Pull Zone Options
| Option | Description |
|---|---|
origin_url |
Origin server URL |
origin_host_header |
Host header sent to origin |
type |
standard or volume |
enabled_regions |
Array of: EU, US, ASIA, SA, AF |
hostnames |
Custom hostnames (SSL auto-provisioned) |
force_ssl |
Force SSL for custom hostnames (true/false) |
edge_rules |
Array of edge rule configurations |
Edge Rules
{
"edge_rules": [
{
"description": "CORS Headers",
"enabled": true,
"trigger_match": "any",
"triggers": [
{"type": "url", "match": "any", "patterns": ["*"]}
],
"actions": [
{"type": "set_response_header", "header": "Access-Control-Allow-Origin", "value": "*"}
]
}
]
}
Trigger types: url, url_extension, url_query_string, request_header, response_header, country_code, remote_ip, status_code, request_method, random_chance
Action types: set_response_header, set_request_header, redirect, block, force_ssl, override_cache_time, origin_url, force_download, disable_token_auth, enable_token_auth, override_cache_time_public, ignore_query_string, disable_optimizer, force_compression, set_status_code, bypass_perma_cache
Usage
Push (local → bunny.net)
# Sync a specific domain (recommended)
uv run bunny-dns -c config.json --domain example.com
# Dry run - preview changes without applying
uv run bunny-dns -c config.json --domain example.com --dry-run
# DNS only
uv run bunny-dns -c config.json --domain example.com --dns-only
# Pull zones only
uv run bunny-dns -c config.json --domain example.com --pullzones-only
# Additive mode - don't delete records not in config
uv run bunny-dns -c config.json --domain example.com --no-delete
# Sync all domains in config
uv run bunny-dns -c config.json
Pull (bunny.net → local)
Export your current bunny.net configuration as JSON — useful for bootstrapping a config from existing infrastructure or verifying drift.
# Pull a specific domain
uv run bunny-dns --sot bunny --domain example.com
# Pull all DNS zones on the account
uv run bunny-dns --sot bunny --all
# Write to file instead of stdout
uv run bunny-dns --sot bunny --domain example.com -o config.json
# Pull DNS only or pull zones only
uv run bunny-dns --sot bunny --domain example.com --dns-only
uv run bunny-dns --sot bunny --domain example.com --pullzones-only
CLI Options
| Option | Description |
|---|---|
-c, --config |
Path to JSON configuration file (required for push) |
-d, --domain |
Only sync/pull this specific domain |
-n, --dry-run |
Preview changes without applying (push only) |
--dns-only |
Only sync/pull DNS zones |
--pullzones-only |
Only sync/pull Pull Zones |
--no-delete |
Don't delete records not in config (push only) |
--sot |
Source of truth: local (default, push) or bunny (pull) |
--all |
Pull all DNS zones on the account (with --sot bunny) |
-o, --output |
Write pull output to file instead of stdout |
--api-key |
API key (defaults to BUNNY_API_KEY env var) |
Check Propagation Status
After updating nameservers, check if DNS has propagated:
# Basic check
uv run bunny-dns-check example.com
# Include pull zone hostname/SSL checks
uv run bunny-dns-check example.com -c config.json
Output:
NAMESERVERS: ✓ OK
Expected: coco.bunny.net, kiki.bunny.net
Current: kiki.bunny.net, coco.bunny.net
→ DNS is pointing to bunny.net
DNS RECORDS:
✓ A @ → 185.199.110.153, 185.199.111.153
✓ AAAA @ → 2606:50c0:8000::153, 2606:50c0:8003::153
✓ MX @ → 0 mail.example.com
PULL ZONE HOSTNAMES:
✓ cdn.example.com
CNAME: my-cdn.b-cdn.net
SSL: ok (200)
Workflow: Migrating to Bunny DNS
If you already have DNS elsewhere and want to move to bunny.net, you can bootstrap your config from your existing setup using pull, then push it:
# Export your current bunny.net state (if zone already exists)
uv run bunny-dns --sot bunny --domain example.com -o config.json
# Or create config.json manually, then:
uv run bunny-dns -c config.json --domain example.com --dry-run # preview
uv run bunny-dns -c config.json --domain example.com # apply
Then update nameservers at your registrar to kiki.bunny.net and coco.bunny.net, verify with uv run bunny-dns-check, and re-run sync to load SSL certificates. See the Fathom Analytics section below for a full step-by-step example.
Safety Features
- Domain isolation - Only touches domains explicitly in your config
- Dry-run mode - Preview all changes before applying
- No-delete mode - Additive only, never removes records
- Rate limit handling - Auto-retry with exponential backoff
- Explicit domain flag -
--domainensures you only sync what you intend
Development
git clone https://github.com/mrpesho/bunny-dns-sync.git
cd bunny-dns-sync
uv pip install -e ".[dev]"
Testing
The project includes a comprehensive test suite with 99% code coverage.
# Run all tests
uv run python -m pytest tests/ -v
# Run with coverage report
uv run python -m pytest tests/ --cov=bunny_dns --cov-report=term-missing
# Run specific test file
uv run python -m pytest tests/test_dns_manager.py -v
Test Structure
| File | Tests | Coverage |
|---|---|---|
test_bunny_client.py |
HTTP client, retries, exceptions | 100% |
test_dns_manager.py |
DNS records, normalization, sync | 100% |
test_pullzone_manager.py |
Pull zones, hostnames, regions | 99% |
test_edge_rules_manager.py |
Edge rules, action/trigger parsing | 100% |
test_sync.py |
Orchestrator, config loading | 99% |
Use Case: Fathom Analytics Proxy
Ad blockers block requests to known analytics domains like cdn.usefathom.com. By proxying Fathom Analytics through your own subdomain via Bunny CDN, requests come from ana.yourdomain.com instead — which blockers don't recognize.
1. Create your config
Create a config.json that sets up both the DNS CNAME record and a Bunny Pull Zone for your domain. Replace example.com with your actual domain and ana-example with a unique pull zone name:
{
"domains": {
"example.com": {
"dns_records": [
{"type": "CNAME", "name": "ana", "value": "ana-example.b-cdn.net", "ttl": 3600}
],
"pull_zones": {
"ana-example": {
"origin_url": "https://cdn.usefathom.com",
"origin_host_header": "cdn.usefathom.com",
"type": "standard",
"enabled_regions": ["EU", "US"],
"hostnames": ["ana.example.com"],
"edge_rules": []
}
}
}
}
}
This configures:
- A CNAME record pointing
ana.example.comto your Bunny pull zone - A Pull Zone that proxies requests to Fathom's CDN
- A custom hostname with automatic free SSL
2. Preview changes
uv run bunny-dns -c config.json --domain example.com --dry-run
3. Apply
uv run bunny-dns -c config.json --domain example.com
Note: If your domain's nameservers are not yet pointing to bunny.net, SSL certificate provisioning will fail on the first run. That's expected — it will succeed after you update nameservers.
4. Update nameservers (if not already on bunny.net)
At your domain registrar, set the nameservers to:
kiki.bunny.netcoco.bunny.net
5. Verify propagation
uv run bunny-dns-check example.com -c config.json
6. Re-run to load SSL certificates
uv run bunny-dns -c config.json --domain example.com
The tool detects hostnames missing certificates and loads them automatically.
7. Configure Fathom
In your Fathom dashboard, set ana.example.com as your custom domain. Update your tracking script to use it:
<script src="https://ana.example.com/script.js" data-site="YOUR_SITE_ID" defer></script>
License
MIT
Support
If you're new to bunny.net and find this tool useful, consider signing up through my affiliate link. It helps support continued development at no extra cost to you.
Release files for bunny-dns-sync 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| bunny_dns_sync-0.2.0.tar.gz | 40.9 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| bunny_dns_sync-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 66.9 kB
Release files / bunny_dns_sync-0.2.0.tar.gz
| Download URL | bunny_dns_sync-0.2.0.tar.gz |
|---|---|
| Size | 40.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
8f6ce448d51a497c241a56ca190a5de6bc7740bdbf35c2880b464c593afc0925
|
|
BLAKE2b-256 checksum How to use checksums |
20e6d505c051619f4255c5bb992aa26aa1d0e4b871b1d3226d39cad23315d3a9
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.13.3
|
Release files / bunny_dns_sync-0.2.0-py3-none-any.whl
| Download URL | bunny_dns_sync-0.2.0-py3-none-any.whl |
|---|---|
| Size | 26.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
1b25f44989abfef40dfee7ac6bc86b87a188b5e969d1bb3cd67bc07df0fdde78
|
|
BLAKE2b-256 checksum How to use checksums |
40bc2e7ccb674d02288dc917c3071af8c8f493abc826a83bfff32e55ee307c1d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.13.3
|