This episode covers DNS and FQDN policy: domain-based egress control for external service integration, combining DNS and IP policies in application policies, and the common pitfalls when using domains.

Writing IP-based egress policies for applications that call cloud services is a pain: the IPs change, and you have to keep updating the policy. Episode 11 introduces the solution: FQDN policy — policies that write domains instead of IPs, with Calico translating them into IP rules on the dataplane.
FQDN policy is an ingredient used in almost every production environment, because almost no application talks only to static IPs. In this episode you'll learn to write domain policies, combine them with IP rules, and avoid the DNS pitfalls that make applications time out.
Domain-based policies are written with the domainSelector field on egress rules. Calico watches the DNS queries made by workloads, then builds IP rules from the resolution results. That's why this policy works best for stable FQDNs.
apiVersion: projectcalico.org/v3
kind: NetworkPolicy
metadata:
name: api-egress-external
namespace: payments
spec:
selector: app == 'api'
types:
- Egress
egress:
- action: Allow
protocol: TCP
destination:
domainSelector: 'api.external.example.com'
ports:
- 443The domainSelector above allows outbound traffic only to api.external.example.com. Wildcards with * are also supported, for example '*.example.com' for all subdomains.
When a pod resolves the name api.external.example.com via CoreDNS, Calico captures the result, adds that IP to the set of allowed destinations, then programs the rule on the node:
pod queries DNS -> CoreDNS -> Calico captures the answer
-> IP rule added to the dataplane -> egress allowedBecause rules are built from query results, make sure the workload actually performs DNS resolution inside the cluster — not a hardcoded IP in the application. If the IP is hardcoded, Calico never learns the domain name.
Applications often need two kinds of outbound access at once: to a static IP (for example, an internal database) and to a domain (for example, an external API). Both can be written in one policy:
apiVersion: projectcalico.org/v3
kind: NetworkPolicy
metadata:
name: api-egress-mix
namespace: payments
spec:
selector: app == 'api'
types:
- Egress
egress:
- action: Allow
protocol: TCP
destination:
nets:
- 198.51.100.0/24
ports:
- 5432
- action: Allow
protocol: TCP
destination:
domainSelector: '*.payments.example.com'
ports:
- 443The first rule uses nets for the static IP, the second uses domainSelector for the domain. Calico combines them without conflict.
An egress rule to a domain is only useful if the DNS query itself can be resolved. So make sure there's an allow rule to CoreDNS. The most common pattern:
egress:
- action: Allow
protocol: UDP
destination:
namespaceSelector: k8s-app == 'kube-dns'
selector: k8s-app == 'kube-dns'
ports:
- 53Without this rule, resolution fails, and an FQDN policy never gets a destination IP.
The destination IP can change after the TTL expires. Calico holds the resolution result according to the TTL; if the application keeps using the old IP, connections can fail. The fix isn't to raise the TTL arbitrarily, but to make sure the application re-resolves as usual.
Note that FQDN policy works at the IP/port level — it does not inspect SNI or the host header. Two domains that resolve to the same IP will be subject to the same rule. For L7 control that sees the actual domain name, you need Application Layer Policy — the material of episode 12.
Make sure domains don't have a trailing dot (api.example.com. vs api.example.com). This small difference often makes a policy never match.
Start by making sure DNS works, then add the FQDN policy, then verify:
calicoctl apply -f allow-dns.yaml
calicoctl apply -f api-egress-external.yaml
kubectl exec -n payments deploy/api -- nslookup api.external.example.com
kubectl exec -n payments deploy/api -- curl -sS https://api.external.example.comkubectl exec ... nslookup confirms resolution succeeds inside the cluster, and curl -sS tests real access to the domain.
To make sure the domain IPs have entered the dataplane:
kubectl exec -n calico-system ds/calico-node -- iptables -L cali-FWR-IN-3 -n
calicoctl get networkpolicy -n payments -o yaml | grep -A3 domainSelectorEpisode 11 equips you with domain-based egress: writing domainSelector, combining DNS and IP rules, and avoiding the TTL, SNI, and name-format pitfalls that silently make policies fail.
Key takeaways:
domainSelector enables FQDN-based egress; the * wildcard is supported.nets and domainSelector in one policy as needed.In the next episode, episode 12, we cover service mesh and app layer — ApplicationLayerPolicy with the Envoy integration, L7 visibility, HTTP action detection, and Calico's position in service mesh traffic.