Skip to content
CoreDNS

CoreDNS

Setup CoreDNS in Docker container:

Create docker compose:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
services:
  coredns:
    image: coredns/coredns:latest
    container_name: coredns
    command: -conf /etc/coredns/Corefile -dns.port 53
    ports:
      - "53:53"
      - "53:53/udp"
      - "8080:8080"
    volumes:
      - ./corefile/Corefile:/etc/coredns/Corefile
      - ./coredns-zones:/etc/coredns/zones:ro
    restart: unless-stopped

Create Corefile in corefile folder, this folder configures the coreDNS server:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
. {
    errors
    health
    prometheus
    log
    forward . 1.1.1.1 {
        except 
    }
}

name.domain.com:53 {
    file /etc/coredns/zones/db.name.domain.com
}

create zone files in the coredns-zones folder, these files contain the DNS records for each dns zone:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
$TTL 3600
$ORIGIN yourdomain.internal.

@       IN  SOA     ns1.yourdomain.internal. admin.yourdomain.internal. (
                2025021501 3600 1800 604800 86400 )

@       IN  NS      ns1.yourdomain.internal.

@               IN  A       192.168.1.10
router          IN  A       192.168.1.1
pihole          IN  A       192.168.1.55
nas             IN  A       192.168.1.20
printer         IN  A       192.168.1.30

www             IN  CNAME   server.yourdomain.internal.

Full example of Corefile:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
# =====================================================================
# Example CoreDNS Corefile for DNS
#
# Purpose:
#   - Serve authoritative DNS for your internal domains (e.g. lab.internal, home.arpa)
#   - Forward everything else to public resolvers (with optional exceptions)
#   - Basic observability, logging, and health checks
#
# Recommended file location: /etc/coredns/Corefile
# =====================================================================

# Global settings that apply to all zones
{
    # Automatically reload config/zone files when they change on disk
    # Very useful during testing or when updating records
    reload 30s 5s

    # Log all queries (useful for debugging; comment out in production)
    log

    # Show health endpoint (/health) and Prometheus metrics (/metrics)
    health
    prometheus

    # Handle errors nicely
    errors

    # Optional: cache responses to improve performance
    cache 30
}

# =====================================================================
# Catch-all / default forwarding for everything not explicitly defined
# =====================================================================
. {
    # Forward non-internal queries to public resolvers
    forward . 1.1.1.1 1.0.0.1 8.8.8.8 8.8.4.4 {
        # Do NOT forward these domains — handle them authoritatively below
        except internal.example.com home.arpa lan home
    }

    # Optional: block ads/trackers at DNS level (requires a hosts file or blocklist plugin)
    # hosts /etc/coredns/blocklist.hosts {
    #     fallthrough
    # }
}

# =====================================================================
# Authoritative zones (your internal domains)
# Use the "file" plugin to load classic BIND-style zone files
# =====================================================================

# Main internal domain
internal.example.com:53 {
    file /etc/coredns/zones/db.internal.example.com
    # Optional: allow zone transfers to known secondaries (rarely needed in small setups)
    # transfer {
    #     to 192.168.50.20
    # }
}

# Reverse DNS for your LAN subnet (example: 192.168.50.0/24)
50.168.192.in-addr.arpa:53 {
    file /etc/coredns/zones/db.50.168.192.in-addr.arpa
}

# Another internal domain (e.g. for IoT devices)
iot.home.arpa:53 {
    file /etc/coredns/zones/db.iot.home.arpa
}

# Short local domain (very common in homelabs)
lan:53 {
    file /etc/coredns/zones/db.lan
}

# =====================================================================
# Optional: wildcard / catch-all for testing or dynamic environments
# =====================================================================
# *.internal.example.com:53 {
#     file /etc/coredns/zones/db.wildcard.internal.example.com
# }

# =====================================================================
# Optional: mDNS / .local support (multicast DNS) - only if needed
# =====================================================================
# local:53 {
#     mdns
#     forward . 224.0.0.251:5353   # multicast address
# }

Full example zone file:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
; =====================================================================
; Example CoreDNS zone file (BIND/RFC 1035 style)
; Used with the "file" plugin in CoreDNS
;
; File name convention: db.  (e.g. db.internal.example.com)
; Place in: /etc/coredns/zones/ or similar mounted directory
;
; Best practice: keep one zone file per domain/zone
; =====================================================================

$TTL 3600       ; default TTL for records without explicit TTL (1 hour)
$ORIGIN internal.example.com.   ; base domain - all unqualified names are relative to this

; =====================================================================
; SOA Record (required for a proper authoritative zone)
; =====================================================================
@       IN  SOA     ns1.internal.example.com. hostmaster.internal.example.com. (
                2026021501  ; serial number   YYYYMMDDnn format - increment on every change
                3600        ; refresh         how often secondary servers check for updates
                1800        ; retry           how long to wait before retrying failed refresh
                604800      ; expire          when secondary servers should stop serving zone
                86400 )     ; minimum / negative cache TTL

; =====================================================================
; NS Records (nameservers for this zone)
; =====================================================================
@       IN  NS      ns1.internal.example.com.
@       IN  NS      ns2.internal.example.com.   ; optional second NS

; =====================================================================
; Common records - apex / root of zone
; =====================================================================
@               IN  A       192.168.50.10       ; main server / router / traefik ingress
@               IN  AAAA    fd12:3456::10       ; IPv6 example (optional)

; =====================================================================
; A / AAAA records (most common)
; =====================================================================
router          IN  A       192.168.50.1
printer         IN  A       192.168.50.20
nas             IN  A       192.168.50.30
nas             IN  AAAA    fd12:3456::30
pihole          IN  A       192.168.50.40
homeassistant   IN  A       192.168.50.50
proxmox1        IN  A       192.168.50.60
proxmox2        IN  A       192.168.50.61

; Short aliases / CNAMEs
www             IN  CNAME   traefik.internal.example.com.
dashboard       IN  CNAME   traefik.internal.example.com.
grafana         IN  CNAME   traefik.internal.example.com.

; =====================================================================
; MX record (if you run internal mail)
; =====================================================================
@               IN  MX  10  mail.internal.example.com.

mail            IN  A       192.168.50.70

; =====================================================================
; TXT records (SPF, verification, etc.)
; =====================================================================
@               IN  TXT     "v=spf1 a mx ~all"
_dmarc          IN  TXT     "v=DMARC1; p=none; rua=mailto:dmarc-reports@internal.example.com;"

; =====================================================================
; SRV records (useful for discovery - e.g. Home Assistant, mDNS-like, SIP, etc.)
; =====================================================================
; Example: _http._tcp SRV for web services
_http._tcp      IN  SRV     0 5 80 traefik.internal.example.com.

; Example: Home Assistant companion app discovery
_hassio._tcp    IN  SRV     0 0 8123 homeassistant.internal.example.com.

; =====================================================================
; Wildcard example (catch-all for undefined subdomains)
; Be careful - only use if you really want this behavior
; =====================================================================
; *             IN  A       192.168.50.10       ; everything → traefik ingress

; =====================================================================
; Reverse DNS / PTR records belong in separate in-addr.arpa zones
; Example for 192.168.50.0/24 → you would create db.50.168.192.in-addr.arpa
; =====================================================================
; 10              IN  PTR     router.internal.example.com.
; 20              IN  PTR     printer.internal.example.com.
; etc.
; =====================================================================