Apple Container Networking: How It Works and What Modes Are Supported

Apple Container implements networking on top of the macOS vmnet framework, supporting two distinct modes—nat (default) and hostOnly—which are enforced by the container-network-vmnet XPC service and defined in the NetworkMode enum.

The apple/container project leverages macOS-native virtualization APIs rather than Linux-style bridge networking to connect containers to the host and external networks. When you run container system start, the system initializes a virtual Ethernet switch using the vmnet framework, creating a default network that automatically assigns IP addresses to containers from a reserved subnet. This architecture ensures secure isolation while allowing flexible configuration through NAT or host-only segmentation.

Core Networking Architecture

Apple Container builds its networking stack directly on the vmnet framework, a macOS API that provides virtual NICs and an Ethernet switch for connecting containers to the host. When the container system start command executes, it launches the container-network-vmnet XPC service, which runs in the background to manage virtual interfaces.

According to the technical documentation in docs/technical-overview.md, this helper service performs three critical functions:

  • Allocates IP addresses from predefined subnets (defaulting to 192.168.64.0/24)
  • Configures virtual Ethernet interfaces for each container
  • Forwards traffic between the host and containers based on the selected network mode

Every container attaches to the default vmnet network unless a custom network is specified via the --network flag.

Supported Network Modes

The networking layer supports exactly two operational modes, defined in Sources/ContainerResource/Network/NetworkMode.swift as a Swift enum:

enum NetworkMode {
    case nat
    case hostOnly
}

Concrete implementations in Sources/Services/NetworkVmnet/Server/ReservedVmnetNetwork.swift and AllocationOnlyVmnetNetwork.swift enforce these modes. If you attempt to configure an unsupported mode, the system returns an error such as "invalid network mode" and rejects the network creation request.

NAT Mode (Default)

NAT mode is the default configuration where containers receive IP addresses from a private subnet (e.g., 192.168.64.0/24) but cannot be reached directly from external networks. The host performs Network Address Translation, allowing outbound connections from containers while isolating them from inbound traffic unless explicitly exposed.

To allow external access to a service running inside a container, you must publish the port using the --publish (-p) flag:

container run -d --name web --network default -p 127.0.0.1:8080:80 nginx:latest

In this example, the container receives an address like 192.168.64.2/24 from the vmnet helper, but external clients can only reach it through localhost:8080 on the host.

Host-Only Mode

Host-only mode creates an isolated subnet where containers can communicate with each other but cannot reach external networks or the internet. This mode is useful for isolated testing environments where you need inter-container communication without exposing services externally.

Create a host-only network and attach a container using:

container network create isolated --mode hostOnly
container run --network isolated ubuntu:latest ip addr show eth0

The container will show an IP address from the host-only subnet, but attempts to reach external addresses will fail unless traffic is explicitly forwarded through the host.

macOS Version Limitations

Networking capabilities vary significantly based on the host macOS version:

  • macOS 26 or later: Full support for user-defined networks, multiple concurrent networks, and both NAT and host-only modes
  • macOS 15: The vmnet framework only provides isolated networks. All containers must attach to the default network; the container network commands are unavailable, and the --network flag will error out. Container-to-container communication over the virtual network is not possible on this version

This limitation is documented in docs/technical-overview.md under the network isolation section.

Configuring Container Networks

Creating Custom Networks

On supported macOS versions, you can create isolated networks with specific subnets using container network create:


# Create a custom NAT network with specific CIDR

container network create frontend --subnet 192.168.65.0/24

# Create an IPv6-enabled network

container network create backend --subnet-v6 fd00:dead:beef::/64

Each custom network is isolated from others; containers attached to frontend cannot reach containers on backend unless explicitly routed through the host.

Attaching Containers and Publishing Ports

When running a container, specify the network using the --network flag with optional MAC address and MTU parameters:

container run --network default,mac=02:42:ac:11:00:02,mtu=1500 ubuntu:latest

The syntax follows --network <name>[,mac=XX:XX:XX:XX:XX:XX][,mtu=VALUE]. The vmnet helper validates the MAC address uniqueness and configures the interface accordingly.

For NAT networks, remember that inbound connections require port publishing. The host listens on the specified address and forwards traffic to the container's internal port.

Inspecting Network Configuration

Verify a container's network attachment using container inspect, which returns a JSON networks array containing the assigned IP, gateway, and network name:

container inspect web | jq '.networks[0]'

Example output includes:

  • address: The assigned IP with CIDR notation (e.g., "192.168.64.2/24")
  • gateway: The gateway address for the subnet
  • name: The network identifier (e.g., "default" or "frontend")

Network Cleanup

Remove unused networks with:

container network delete frontend
container network prune  # Remove all unused networks

The system will prevent deletion if any running containers remain attached to the network.

Practical Examples


# 1. Create a custom isolated network named "foo" (default NAT mode)

container network create foo

# 2. Run a container attached to that network, publishing port 8080 → 80

container run -d --name web --network foo -p 127.0.0.1:8080:80 nginx:latest

# 3. Verify the container's address inside the network

container inspect web | jq '.networks[0].address'

# → "192.168.65.2/24" (foo's subnet)

# 4. Use a host-only network (no address translation)

container network create bar --mode hostOnly
container run --network bar ubuntu:latest ip addr show eth0

# 5. Set a deterministic MAC address (useful for repeatable tests)

container run --network default,mac=02:42:ac:11:00:02 ubuntu:latest \
    cat /sys/class/net/eth0/address

# → 02:42:ac:11:00:02

# 6. Delete an unused network

container network delete foo

Summary

  • Apple Container networking relies on the vmnet framework and the container-network-vmnet XPC service to manage virtual interfaces and IP allocation
  • Two network modes are supported: nat (default) for outbound internet access via address translation, and hostOnly for isolated container-to-container communication
  • Configuration is defined in Sources/ContainerResource/Network/NetworkMode.swift and enforced by ReservedVmnetNetwork.swift and AllocationOnlyVmnetNetwork.swift
  • macOS 15 has significant limitations, supporting only the default network with no custom network creation, while macOS 26+ supports multiple user-defined networks with both NAT and host-only modes
  • Containers attach to the default network (192.168.64.0/24) automatically, but can be assigned to custom networks using --network with optional MAC and MTU parameters
  • External access requires explicit port publishing via --publish or -p flags

Frequently Asked Questions

What is the difference between NAT and host-only mode in Apple Container?

NAT mode (the default) allows containers to reach external networks through Network Address Translation performed by the host, while keeping container IPs private and non-routable from outside. Host-only mode creates an isolated subnet where containers can communicate with each other but cannot access the internet or external networks unless traffic is explicitly forwarded through the host. Both modes are defined in NetworkMode.swift and validated by the vmnet network server implementations.

Why can't I create custom networks on macOS 15?

On macOS 15, the vmnet framework only supports isolated network configurations, meaning all containers must attach to the single default network. The container network commands are disabled on this version, and attempting to use --network with a custom name will result in an error. Container-to-container communication over the virtual network is not possible on macOS 15. You must upgrade to macOS 26 or later to create user-defined networks with specific subnets and modes.

How do I expose a container port to my Mac host?

For containers running in NAT mode, you must use the --publish (or -p) flag when running the container to map a host port to a container port. For example, container run -p 127.0.0.1:8080:80 nginx maps port 80 in the container to port 8080 on localhost. In host-only mode, the container is already accessible on its assigned IP address within the host-only subnet, but external access still requires port forwarding configuration if needed.

Can I assign a specific MAC address to my container?

Yes, you can specify a deterministic MAC address when attaching a container to a network using the extended --network syntax: --network <name>,mac=02:42:ac:11:00:02. This is particularly useful for testing scenarios requiring consistent network identifiers. The vmnet helper validates the MAC address format and ensures uniqueness before configuring the virtual Ethernet interface.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →