- Go 70.1%
- HTML 28.3%
- Makefile 1.1%
- Dockerfile 0.5%
|
All checks were successful
Dev pipeline / build-and-push (push) Successful in 40s
|
||
|---|---|---|
| .gitea/workflows | ||
| docs | ||
| pkg/dnsapi | ||
| .gitignore | ||
| AGENTS.md | ||
| db.sqlite | ||
| docker-compose.yml | ||
| Dockerfile | ||
| go.mod | ||
| go.mod.backup | ||
| go.sum | ||
| go.sum.backup | ||
| LICENSE | ||
| main.go | ||
| Makefile | ||
| pipeline.yml | ||
| README.md | ||
| requests.http | ||
| Taskfile.yml | ||
DNS API
Lightweight REST API for managing Bind9 zones and records across a primary/secondary nameserver setup.
The service runs in two modes:
- Primary — exposes the full zone/record CRUD API, persists data in SQLite, writes zone files and bind config to the local filesystem, and pushes updates to secondary instances via HTTP.
- Secondary — exposes only the bind-sync endpoints used by the primary to deliver zone files and named.conf. It writes received files to disk and reloads bind9.
Both modes are provided by the same binary; the runtime mode is controlled by DNSAPI_MODE.
Quick start (Docker Compose)
cp docker-compose.yml.example docker-compose.yml # adjust tokens and nameserver FQDNs
docker compose up -d
See docker-compose.yml for a full example with comments explaining the rndc setup.
Modes
Primary mode
The primary instance:
- Serves the REST API (zones, records, commit, export/import).
- On commit: writes the zone file locally, pushes it to each secondary via
PUT /bind/zones/:domain, regenerates and writesnamed.conf.rostilocally, and pushes it to each secondary viaPUT /bind/config. - Reloads bind9 via the configured reload command after updating local config/zones.
- 10 seconds after commit, forces an
rndc refreshon itself and all secondaries.
Secondary mode
The secondary instance:
- Exposes only the bind-sync endpoints (protected by the same API token as primary).
- On receiving a zone file or bind config via HTTP, writes it to disk and reloads bind9.
Configuration
All settings are environment variables with the DNSAPI_ prefix.
| Variable | Default | Description |
|---|---|---|
DNSAPI_MODE |
primary |
Runtime mode: primary or secondary |
DNSAPI_API_TOKEN |
(required) | Bearer token for all API calls |
DNSAPI_PORT |
1323 |
HTTP listen port (ignored when HTTPS is enabled) |
DNSAPI_PRIMARY_NAME_SERVER |
(required in primary) | FQDN of the primary nameserver |
DNSAPI_NAME_SERVERS |
(required in primary) | Comma-separated list of all nameservers (≥2) |
DNSAPI_PRIMARY_NAME_SERVER_IP |
(auto-resolved) | Override for the primary NS IP — set this in containers/environments where the FQDN is not resolvable |
DNSAPI_SECONDARY_NAME_SERVER_IPS |
(auto-resolved) | Override for the secondary NS IPs (zone allow-transfer / masters) — set this alongside the primary IP override |
DNSAPI_SECONDARY_INSTANCES |
(empty) | Comma-separated HTTP URLs of secondary dnsapi instances |
DNSAPI_ABUSE_EMAIL |
(required in primary) | SOA abuse email |
DNSAPI_DATABASE_PATH |
gorm.sqlite |
SQLite database path (primary only) |
DNSAPI_BIND_RELOAD_COMMAND |
systemctl reload bind9 |
Shell command used to reload bind9 |
DNSAPI_BIND_REFRESH_COMMAND |
rndc refresh |
Command prefix for zone refresh (zone name appended) |
DNSAPI_HTTPS |
false |
Enable HTTPS with automatic Let's Encrypt certificate |
DNSAPI_PUBLIC_DOMAIN |
(required when HTTPS) | Domain for the TLS certificate |
DNSAPI_ACME_EMAIL |
(required when HTTPS) | Email for Let's Encrypt registration |
DNSAPI_SENTRY_DSN |
(empty) | Sentry DSN for error reporting |
DNSAPI_SENTRY_ENV |
dev |
Sentry environment tag |
DNSAPI_TIME_TO_REFRESH |
300 |
SOA refresh interval |
DNSAPI_TIME_TO_RETRY |
180 |
SOA retry interval |
DNSAPI_TIME_TO_EXPIRE |
604800 |
SOA expire value |
DNSAPI_MINIMAL_TTL |
30 |
SOA minimum TTL |
DNSAPI_TTL |
3600 |
Default record TTL |
Installation (systemd)
Primary (runs on the same host as bind9):
[Unit]
Description=dnsapi — primary
After=network.target
[Service]
WorkingDirectory=/etc/bind
Environment=DNSAPI_MODE=primary
Environment=DNSAPI_API_TOKEN=<strong-secret>
Environment=DNSAPI_PRIMARY_NAME_SERVER=ns1.example.com
Environment=DNSAPI_NAME_SERVERS=ns1.example.com,ns2.example.com
Environment=DNSAPI_SECONDARY_INSTANCES=https://ns2.example.com:1323
Environment=DNSAPI_ABUSE_EMAIL=admin@example.com
Environment=DNSAPI_DATABASE_PATH=/var/lib/dnsapi/db.sqlite
Environment=DNSAPI_PORT=1323
ExecStart=/usr/local/bin/dnsapi
Restart=on-failure
[Install]
WantedBy=multi-user.target
Secondary (runs on the same host as the secondary bind9):
[Unit]
Description=dnsapi — secondary
After=network.target
[Service]
WorkingDirectory=/etc/bind
Environment=DNSAPI_MODE=secondary
Environment=DNSAPI_API_TOKEN=<same-strong-secret>
Environment=DNSAPI_PORT=1323
ExecStart=/usr/local/bin/dnsapi
Restart=on-failure
[Install]
WantedBy=multi-user.target
HTTPS / Let's Encrypt
Set DNSAPI_HTTPS=true, DNSAPI_PUBLIC_DOMAIN=ns1.example.com, and DNSAPI_ACME_EMAIL=admin@example.com.
The service starts on port 443 (TLS) and opens an HTTP-01 challenge listener on port 80.
Certificates are cached in /var/cache/dnsapi/autocert.
When HTTPS is enabled, DNSAPI_PORT is ignored.
API
Swagger UI is served at /swagger/index.html (primary mode only).
Zones
| Method | Path | Description |
|---|---|---|
GET |
/zones/ |
List all zones |
GET |
/zones/:zone_id |
Get a single zone |
POST |
/zones/ |
Create a zone |
PUT |
/zones/:zone_id |
Update zone tags / abuse email |
DELETE |
/zones/:zone_id |
Delete a zone |
PUT |
/zones/:zone_id/commit |
Write zone to bind and sync to secondaries |
Records
| Method | Path | Description |
|---|---|---|
GET |
/zones/:zone_id/records/ |
List records |
GET |
/zones/:zone_id/records/:record_id |
Get a record |
POST |
/zones/:zone_id/records/ |
Create a record |
PUT |
/zones/:zone_id/records/:record_id |
Update a record |
DELETE |
/zones/:zone_id/records/:record_id |
Delete a record |
Secondary sync endpoints (secondary mode only)
| Method | Path | Description |
|---|---|---|
PUT |
/bind/config |
Write named.conf include file and reload bind9 |
PUT |
/bind/zones/:domain |
Write a zone file |
DELETE |
/bind/zones/:domain |
Delete a zone file |
POST |
/bind/reload |
Reload bind9 |
POST |
/bind/refresh/:domain |
Force zone refresh |
All endpoints require Authorization: <token> header.
Database migrations
Versioned SQL migrations are embedded in the binary (pkg/dnsapi/migrations/*.sql) and applied automatically on startup. Migration state is tracked in the schema_migrations table.
Development
go build ./...
go test ./...
make docs # regenerate Swagger docs