- Go 96.2%
- Makefile 3%
- Dockerfile 0.8%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| .github/workflows | ||
| 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 | ||
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
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.