Skip to content

[Request]: First-class container-to-container DNS discovery on custom networks #1809

Description

@thromel

Feature or enhancement request details

Please add first-class container-to-container DNS discovery for containers attached to the same container network, including bare container names and network aliases.

This came up while testing Compose-compatible workflows with container-compose. Compose users expect service names such as web, db, and network aliases to resolve from peer containers on the same network without a global host DNS setup step.

Related tracking:

Current behavior on container 1.0.0:

container system start
container network create appnet
container run -d --name network-web --network appnet nginx:alpine
container run --rm --network appnet busybox:latest nslookup network-web

The peer container receives the network gateway as its resolver:

container exec network-web cat /etc/resolv.conf
# nameserver 192.168.65.1

The container is reachable by IP from the same network, but name lookup returns NXDOMAIN:

Server:         192.168.65.1
Address:        192.168.65.1:53

** server can't find network-web: NXDOMAIN

The currently documented workaround appears to be configuring a DNS domain with container system dns create <domain> plus setting dns.domain. That can make FQDN/search-domain flows work, but it is not the same as network-scoped, Compose-style service discovery. It also requires global host DNS configuration for what is usually expected to be a per-network container runtime feature.

A useful end state would be one of these:

  1. Containers on the same network can resolve each other by container name, for example web -> that container's network IP.
  2. The network layer supports additional network-scoped aliases so higher-level tools can map Compose service names and aliases onto Apple Container networks.
  3. If bare names are intentionally out of scope, expose a supported CLI/API path that tools can use to create network-scoped DNS domains/aliases without requiring users to manually toggle global DNS settings.

Environment used for the repro:

- OS: macOS 26.5.1 (25F80)
- Xcode: 26.5 (17F42)
- Container: container CLI version 1.0.0 (build: release, commit: ee848e3)
- Architecture: arm64

I agree to follow this project's Code of Conduct.

Activity

  1. thromel commented on Jun 25, 2026

    @thromel
    Author

    Update: I opened a draft implementation PR for the container-facing DNS part:

    What it covers:

    • containers can query their vmnet gateway on UDP port 53
    • responses are scoped to source IPs inside active container networks
    • bare container hostnames resolve through the existing network service
    • external names are forwarded to upstream DNS servers so the listener does not break normal container DNS

    The PR is intentionally draft/RFC because it binds wildcard UDP 53 and currently discovers upstreams from /etc/resolv.conf; both are design choices that should get maintainer review.

    It is stacked on #1810 because the DNS path needs the hostname normalization/trailing-dot behavior from that PR.

    Remaining work for Compose-style parity: explicit network aliases/service aliases still need API/CLI support after the bare-hostname DNS path is settled.

  2. thromel commented on Jun 25, 2026

    @thromel
    Author

    Follow-up draft PR for explicit aliases is open:

    This stacks on #1813 and adds a narrow alias registration path:

    • AttachmentOptions / Attachment carry aliases
    • --network backend,alias=db,alias=database parses repeated aliases
    • network allocation registers aliases as additional DNS names for the same address
    • releasing the attachment removes the primary hostname and aliases together
    • container creation checks aliases for duplicate-name conflicts

    I could not complete live alias validation because the local debug apiserver launch did not become healthy during the smoke-test attempt. Unit/build coverage is included in the PR body, and the installed /usr/local service was restored afterward.

  3. thromel commented on Jun 25, 2026

    @thromel
    Author

    Status update:

    • Add DNS forwarding groundwork #1813 is now marked ready for review. It includes the container-facing DNS listener, standard DNS query validation, source-network scoping, upstream forwarding, and invalid/loopback upstream filtering.
    • Add network attachment aliases #1815 has been rebased on top of the updated Add DNS forwarding groundwork #1813 stack and now includes additional coverage for alias serialization compatibility and client attachment-option plumbing.
    • Add network attachment aliases #1815 remains draft because live alias smoke validation is still blocked by the local debug apiserver launch issue described in the PR body. The in-process parser/allocator/resource/client coverage passes, and the installed /usr/local release service was restored after the smoke attempt.
  4. thromel commented on Jun 25, 2026

    @thromel
    Author

    Status update: #1815 is now ready for review.

    I was able to complete live alias validation by staging the debug build into a temporary install root and starting the service with explicit --install-root/--log-root settings.

    Validated behavior:

    • container run --network codex-alias-live,alias=db,alias=database ... registers both aliases
    • peer containers on the same network resolve db, database, and the primary container name through the network gateway DNS
    • a duplicate alias=db attachment fails with the expected duplicate-hostname error
    • test containers/networks were removed afterward
    • the installed /usr/local release service was restored and verified healthy
  5. Jakeshadow commented on Jun 30, 2026

    @Jakeshadow

    FQDN-based service discovery is one of the biggest differences from Docker Compose networking. In our testing:

    • Container name → FQDN pattern: <container-name>.containers.local
    • This only works within the same container network
    • Cross-network (multi-compose-file) service discovery doesn't work yet

    We documented the FQDN patterns, working examples, and the cross-network workaround (manual host mapping) at maccontainer.dev/installation/ — the troubleshooting section covers this exact DNS issue.

    It's not Docker's DNS-based service discovery, but the FQDN pattern is predictable once you know the naming convention.

  6. thromel commented on Jul 1, 2026

    @thromel
    Author

    Thanks, that matches the same-network constraint I’ve been seeing. I think the useful invariant is “container ID/name + configured DNS domain” rather than a Docker-style per-network service-name registry; depending on local DNS config that can show up as <container>.containers.local, dev.internal, or another configured domain.

    For Compose parity, the remaining gap is still bare service names and aliases (db, database) on the service network, because Compose files generally do not want to depend on the full generated container FQDN. That is what #1815 is trying to cover narrowly: register aliases on the same network attachment and keep duplicate-name checks scoped there.

    I agree cross-network / multi-compose-file discovery is a separate problem and should stay out of this first pass.

  7. devops-thiago commented on Aug 9, 2026

    @devops-thiago

    We needed exactly this for compose-shaped workflows on container 1.2.0, so here is what we measured and what we ended up building, in case either is useful.

    First, port data that complements the #1813 design note. The gateway binds there failed with EADDRNOTAVAIL from the helper. From plain user space, with a container running and the bridge up so the gateway address exists, the failure is different and harder:

    bind 192.168.64.1:53  -> EPERM (with and without SO_REUSEADDR)
    bind 127.0.0.1:80     -> EPERM
    bind 0.0.0.0:53       -> EADDRINUSE
    bind 192.168.64.1:2053 -> ok
    

    Every port below 1024 is EPERM for an unprivileged process on current macOS, both TCP and UDP, on every address we tried. So a container-facing listener on 53 cannot exist host-side for a non-root engine at all. resolv.conf cannot carry a port, which rules out the unprivileged ports that do bind. The only place a :53 listener can live is inside the guest, which is vminitd territory rather than the engine's.

    What we built instead needs no listener. When a container joins a network, the runtime asks the network helper for everything already attached (one new XPC route that returns the current attachments) and writes each peer into the joining guest's /etc/hosts, bare and qualified:

    192.168.64.2 dnsweb dnsweb.container.internal
    

    The guest's resolv.conf also gets search container.internal when no domain was configured. Since getaddrinfo consults hosts before DNS, service names resolve inside the containers with no resolver in the path and no root anywhere. Verified by starting nginx as dnsweb, then a second container, and fetching http://dnsweb/ and http://dnsweb.container.internal/ from it.

    The limitation is honest: hosts files are written at boot, so a container does not learn about peers that start after it. A stack brought up in dependency order, which is what compose does, sees every name it needs. Aliases from #1815 would slot into the same hosts entries.

    The patch is small (allocator enumeration, the XPC route, client call, hosts composition in the runtime, about 150 lines). Glad to open a PR if the maintainers want it as an interim step while the DNS listener work in #1813 evolves.

  8. thromel commented on Aug 10, 2026

    @thromel
    Author
  9. l-hedgehog commented on Aug 22, 2026

    @l-hedgehog

    Hi @thromel, I asked deepseek-v4-flash-0731 to build a customized vminitd image as described in this doc that attachs some eBPF which will redirect any traffic to a loopback address (hardcoded to 127.0.8.6 for now) at port 53 to a real dns server (defaults to 1.1.1.1:53, but can be set to any other {ip}:{port}). I'm new to both eBPF (where the code looks simple enough and reasonable to me) and vminitd (where the code I didn't even try to read was migrated to aya and much more readable), but it does work as expected. Hopefully this is somewhat useful or at least interesting for the container-facing DNS work. You could try it with
    container run -it --rm --init-image ghcr.io/l-hedgehog/aa-tils/vminitd-ebpf-dns:0.45.0 --kernel-arg dnslink.gateway={ip} --kernel-arg dnslink.port={port} --dns 127.0.8.6 {image}.

  10. andrew-waters commented on Sep 16, 2026

    @andrew-waters

    A downstream data point, since this one is the single biggest compose-compatibility gap I have hit.

    I maintain compose, a container compose plugin, and Orchard. Because hostnames have to be unique across every container on the machine, a service declared as db has to be brought up as myapp-db. That is fine for the container list, and fatal for compose compatibility: essentially every compose file in the wild has postgres://db:5432 or redis://cache hard-coded into an environment variable, because on Docker the service name resolves on the project network.

    So the practical cost of this issue is not "nice DNS ergonomics", it is that compose files cannot be moved across unedited, which is the entire reason people want compose here. Per-network name scoping, as described above, would fix it outright. Happy to test against real projects if that is useful to whoever picks it up.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions