# How Container Handles DNS Resolution Using HostDNSResolver

> Learn how apple/container's HostDNSResolver manages local DNS resolution by configuring /etc/resolver and signaling mDNSResponder for efficient domain query routing.

- Repository: [Apple/container](https://github.com/apple/container)
- Tags: internals
- Published: 2026-06-17

---

**Container's `HostDNSResolver` manages local DNS resolution by creating, removing, and monitoring configuration files in `/etc/resolver` to route domain queries to localhost ports 1053 or 2053, then signals macOS's `mDNSResponder` daemon to reload its configuration.**

Container, Apple's open-source containerization framework, delegates DNS resolution to the host macOS system through a lightweight resolver manager. The `HostDNSResolver` component bridges container networks with the host's DNS infrastructure by manipulating resolver files in the standard macOS resolver directory. This approach allows container-specific domains to resolve locally without modifying system-wide DNS settings.

## HostDNSResolver Architecture and File Locations

The `HostDNSResolver` component operates as a thin abstraction over macOS's resolver directory. According to the source code in [`Sources/Services/ContainerAPIService/Client/HostDNSResolver.swift`](https://github.com/apple/container/blob/main/Sources/Services/ContainerAPIService/Client/HostDNSResolver.swift), the implementation targets `FilePath("/etc/resolver")` as the default configuration path (lines 24-26).

Each resolver file follows a strict naming convention: `containerization.<domain>`. This prefix ensures that Container-managed resolvers are easily distinguishable from system resolvers and can be safely manipulated without affecting other DNS configurations (lines 27-30).

## Creating DNS Resolver Entries

The `createDomain(name:localhost:)` method generates resolver files that point DNS queries to specific localhost ports. When called with a `localhost` IP address, the resolver configures **port 1053** and injects an `options localhost:<ip>` line into the file. Without a localhost specification, it defaults to **port 2053** (lines 37-74).

The generated resolver file contains the `nameserver 127.0.0.1` directive, ensuring that DNS queries for the specified domain are forwarded to the container-side DNS server running on the local machine.

## Removing DNS Configuration

The `deleteDomain(name:)` method handles cleanup by reading the existing resolver file, extracting any embedded localhost IP address using regex pattern matching, and removing the file from `/etc/resolver/` (lines 78-105).

This method returns the extracted IP address as an optional value, allowing callers to perform additional cleanup of associated resources. The regex extraction ensures that Container can track which localhost addresses were previously assigned to specific domains.

## Listing Active Container Domains

To audit currently managed domains, the `listDomains()` method scans the `/etc/resolver` directory and filters files matching the `containerization.` prefix. It parses each file for the `domain` line and returns a sorted set of `DNSName` objects (lines 107-122).

This alphabetical sorting ensures deterministic output for testing and debugging purposes, as implemented in the source code.

## Refreshing the macOS DNS Daemon

After creating or deleting resolver files, the system must notify macOS to reload its DNS configuration. The `reinitialize()` class method sends a `SIGHUP` signal to `mDNSResponder` using the `killall -HUP mDNSResponder` command (lines 124-141).

This signal forces macOS to re-read the resolver files in `/etc/resolver`, making DNS changes immediately effective without requiring a system restart.

## Practical Implementation Example

Below is a complete usage pattern demonstrating domain creation, listing, and deletion using the `HostDNSResolver` API:

```swift
import Foundation
import ContainerizationExtras

// Initialize the resolver with default /etc/resolver path
let resolver = HostDNSResolver()

// Create a domain pointing to localhost port 1053
let domain = DNSName("myapp.local.")
let localhostIP = try IPAddress("127.0.0.1")
try resolver.createDomain(name: domain, localhost: localhostIP)

// Refresh DNS daemon to recognize changes
try HostDNSResolver.reinitialize()

// List all container-managed domains
let domains = resolver.listDomains()
print("Active domains: \(domains.map { $0.pqdn })")

// Delete domain and retrieve associated IP
if let previousIP = try resolver.deleteDomain(name: domain) {
    print("Cleaned up localhost IP: \(previousIP)")
}
try HostDNSResolver.reinitialize()

```

## Summary

- **HostDNSResolver** manages macOS DNS resolution by reading and writing files in `/etc/resolver`.
- Files are prefixed with `containerization.` to avoid conflicts with system resolvers.
- **Port 1053** is used when a localhost IP is specified; otherwise, the resolver defaults to **port 2053**.
- The `deleteDomain(name:)` method extracts localhost IPs using regex before removing files.
- DNS changes take effect after calling `reinitialize()`, which signals `mDNSResponder` with `SIGHUP`.

## Frequently Asked Questions

### What file path does HostDNSResolver use for DNS configuration?

The resolver writes configuration files to `/etc/resolver`, which is the standard macOS directory for domain-specific resolver configurations. Each file is prefixed with `containerization.` followed by the fully-qualified domain name.

### How does Container refresh DNS changes on macOS?

After creating or deleting resolver files, the `reinitialize()` method sends a `SIGHUP` signal to the `mDNSResponder` process using `killall -HUP`. This forces macOS to reload resolver configurations without requiring a system restart.

### What is the difference between port 1053 and 2053 in HostDNSResolver?

When creating a domain with the `localhost` parameter specified, `HostDNSResolver` configures the resolver to use **port 1053** and adds an `options localhost:<ip>` line. If no localhost IP is provided, the resolver defaults to **port 2053** without the options line.

### How does deleteDomain extract the localhost IP address?

The `deleteDomain(name:)` method reads the existing resolver file and applies a regex pattern to extract the localhost IP address embedded in the `options` line. It returns this IP as an optional value, allowing callers to clean up associated network resources after domain removal.