- Go 96.2%
- Makefile 3%
- Dockerfile 0.8%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| .github/workflows | ||
| .woodpecker | ||
| cmd/dns-pihole | ||
| deploy | ||
| internal | ||
| pkg/models | ||
| .dockerignore | ||
| .gitignore | ||
| .golangci.yml | ||
| docker-compose.test.yml | ||
| Dockerfile | ||
| e2e_test.go | ||
| go.mod | ||
| go.sum | ||
| Makefile | ||
| mise.toml | ||
| README.md | ||
| renovate.json | ||
Pi-hole DNS Backend
A DNS provider backend for the homelab-dns-operator that integrates with Pi-hole's custom DNS API.
Overview
This backend implements the DNS Operator webhook protocol, allowing the DNS operator to manage DNS records through Pi-hole's custom DNS feature. It provides REST endpoints for creating, updating, deleting, and querying DNS records.
Features
- ✅ Full implementation of DNS Operator webhook protocol
- ✅ Support for A and AAAA records
- ✅ HMAC authentication support
- ✅ Health check endpoint
- ✅ Idempotent operations
- ✅ Comprehensive error handling
Dependency updates
Renovate runs through Woodpecker every Sunday at 03:45 UTC on main; the
manual pipeline event supports an immediate run. renovate.json covers
Dockerfile bases, Go dependencies, Compose images and actions, with container
digest pinning. Review source updates before merging. The core repository owns
the deployed backend image pin independently of source dependency maintenance.
Prerequisites
- Go 1.21 or later (managed via mise)
- Pi-hole instance with API access enabled
- Pi-hole API password (configured via
FTLCONF_webserver_api_passwordin Docker or web interface)
Configuration
The backend is configured via environment variables:
| Variable | Description | Required | Default |
|---|---|---|---|
PORT |
HTTP server port | No | 7100 |
PIHOLE_URL |
Base URL of Pi-hole instance | Yes | - |
PIHOLE_PASSWORD |
Pi-hole API password | Yes | - |
HMAC_SECRET |
HMAC secret for request authentication | No | - |
Getting Pi-hole API Password
The backend uses password-based authentication with Pi-hole's REST API. You can set the password in one of two ways:
Docker Deployment
Set the FTLCONF_webserver_api_password environment variable when starting Pi-hole:
docker run -e FTLCONF_webserver_api_password="your-password" pihole/pihole
Web Interface
- Log into your Pi-hole web interface
- Navigate to Settings → API / Web interface
- Set or change the API password
- Use this password in the
PIHOLE_PASSWORDenvironment variable
Building
Using mise
# Install dependencies
make deps
# Build the application
make build
Manual build
go build -o dns-pihole ./cmd/dns-pihole
Docker Image
Pre-built multi-architecture Docker images are available on GitHub Container Registry:
docker pull ghcr.io/morten-olsen/homelab-dns-pihole:latest
Images are automatically built for linux/amd64 and linux/arm64 architectures.
Running
Local development
export PIHOLE_URL="http://pihole.local:80"
export PIHOLE_PASSWORD="your-api-password"
export HMAC_SECRET="your-hmac-secret" # Optional
export PORT="7100" # Optional
./dns-pihole
Docker
# Build the image
make docker-build
# Run the container
docker run -p 7100:7100 \
-e PIHOLE_URL=http://pihole:80 \
-e PIHOLE_PASSWORD=your-password \
-e HMAC_SECRET=your-secret \
dns-pihole:latest
Kubernetes
See the deploy/ directory for Kubernetes deployment examples.
API Endpoints
Health Check
GET /health
Returns the health status of the backend and Pi-hole connectivity.
Response:
{
"status": "healthy",
"message": "DNS server is operational",
"timestamp": "2025-12-30T10:00:00Z"
}
Create/Update Record
POST /records
Creates or updates a DNS record.
Request:
{
"record": {
"type": "A",
"domain": "example.com",
"subdomain": "www",
"values": ["192.168.1.100"],
"ttl": 600
},
"operation": "upsert"
}
Response:
{
"success": true,
"record": {
"type": "A",
"domain": "example.com",
"subdomain": "www",
"fqdn": "www.example.com",
"values": ["192.168.1.100"],
"ttl": 600
},
"message": "Record created successfully"
}
Get Record
GET /records/{type}/{domain}/{subdomain}
Retrieves a DNS record.
Response:
{
"success": true,
"record": {
"type": "A",
"domain": "example.com",
"subdomain": "www",
"fqdn": "www.example.com",
"values": ["192.168.1.100"],
"ttl": 600
}
}
Delete Record
DELETE /records/{type}/{domain}/{subdomain}
Deletes a DNS record.
Response:
{
"success": true,
"message": "Record deleted successfully"
}
Limitations
- CNAME Records: Pi-hole's custom DNS feature does not support CNAME records. The backend will return an error if you attempt to create a CNAME record. Use A records instead.
- Multiple IPs: Pi-hole custom DNS only supports a single IP address per domain. If multiple values are provided, only the first one will be used.
- TTL: Pi-hole custom DNS does not support custom TTL values. The TTL field in requests is accepted but not applied.
Testing
The project includes comprehensive unit tests with 72%+ code coverage and end-to-end tests against a real Pi-hole instance.
Running Tests
# Run unit tests
make test
# Run tests with coverage report
make test-coverage
# Run tests with race detector
make test-race
# Run e2e tests (requires Docker)
make test-e2e
# Run all tests (unit + e2e)
make test-all
Unit Test Coverage
The unit test suite covers:
- ✅ HTTP handlers (health, upsert, get, delete)
- ✅ Pi-hole API client integration (mocked)
- ✅ HMAC authentication and verification
- ✅ Request validation (IPv4, IPv6, CNAME)
- ✅ Error handling and edge cases
- ✅ Server middleware and routing
End-to-End Tests
The e2e tests run against a real Pi-hole instance in Docker:
- ✅ Health check against real Pi-hole
- ✅ Create and retrieve A/AAAA records
- ✅ Update existing records
- ✅ Delete records (including idempotent behavior)
- ✅ Full HTTP endpoint integration
The e2e tests use Docker Compose to spin up a Pi-hole instance for testing. See docker-compose.test.yml for the test environment configuration.
Manual Testing
# Test health endpoint
curl http://localhost:7100/health
# Test creating a record (with HMAC if configured)
curl -X POST http://localhost:7100/records \
-H "Content-Type: application/json" \
-H "X-DNS-Timestamp: $(date -u +%Y-%m-%dT%H:%M:%SZ)" \
-H "X-DNS-Nonce: $(uuidgen)" \
-H "X-DNS-Signature: <calculated-hmac-signature>" \
-d '{
"record": {
"type": "A",
"domain": "example.com",
"subdomain": "test",
"values": ["192.168.1.100"]
},
"operation": "upsert"
}'
Security
- HMAC Authentication: When
HMAC_SECRETis configured, all requests must include valid HMAC signatures in theX-DNS-Signatureheader. - API Password: The Pi-hole API password should be stored securely (e.g., Kubernetes secrets).
- Network: Consider running the backend in the same network as Pi-hole or using TLS for communication.
CI/CD
The project uses GitHub Actions for continuous integration and deployment:
Test Workflow (.github/workflows/test.yml)
Runs on every push and pull request:
- ✅ Unit tests with race detector (Go 1.21 and 1.22)
- ✅ Linting with golangci-lint
- ✅ End-to-end tests against Dockerized Pi-hole
- ✅ Code coverage reports uploaded as artifacts
Build Workflow (.github/workflows/build.yml)
Builds and pushes multi-architecture Docker images:
- ✅ Multi-architecture builds (
linux/amd64,linux/arm64) - ✅ Pushes to GitHub Container Registry (
ghcr.io) - ✅ Automatic tagging:
latestfor main branchmain-<sha>for commits- Semantic version tags (
v1.0.0,v1.0,v1) for releases
- ✅ Docker layer caching for faster builds
- ✅ OCI image labels with metadata
Image Registry: ghcr.io/morten-olsen/homelab-dns-pihole
See .github/workflows/README.md for detailed workflow documentation.
Development
Linting
The project uses golangci-lint for code quality checks.
# Install golangci-lint (if not already installed)
make install-lint
# Run linters
make lint
# Run linters and auto-fix issues
make lint-fix
The linting configuration is in .golangci.yml. Common checks include:
- Error handling verification
- Code simplification
- Security checks
- Unused code detection
- Code formatting
Project Structure
The project follows the standard Go project layout:
.
├── cmd/
│ └── dns-pihole/
│ └── main.go # Application entry point
├── internal/
│ ├── auth/
│ │ ├── hmac.go # HMAC authentication
│ │ └── hmac_test.go # HMAC tests
│ ├── handlers/
│ │ └── handlers.go # HTTP request handlers
│ ├── pihole/
│ │ ├── client.go # Pi-hole API client
│ │ ├── interface.go # Pi-hole client interface
│ │ ├── mock.go # Mock Pi-hole client for testing
│ │ └── pihole_test.go # Pi-hole client tests
│ └── server/
│ ├── server.go # HTTP server setup and routing
│ └── server_test.go # Server tests
├── pkg/
│ └── models/
│ └── records.go # Shared data models
├── deploy/ # Kubernetes deployment manifests
├── e2e_test.go # End-to-end tests
├── go.mod # Go module definition
├── Dockerfile # Docker build configuration
├── Makefile # Build automation
└── README.md # Project documentation
License
See LICENSE file for details.
Contributing
Contributions are welcome! Please open an issue or submit a pull request.