How to Migrate Containers Between Networks and Troubleshoot IP Assignment in Apple Container

Migrate containers between networks by disconnecting them from the current network and reconnecting to the target using ContainerClient.detachNetwork() and attachNetwork(), or the CLI commands container network disconnect and connect, while troubleshooting IP assignment by verifying CIDR validity and subnet uniqueness in NetworkConfiguration.

The Apple container repository implements a full-stack container runtime that communicates with a local container-apiserver over XPC. When you need to migrate containers between networks or resolve IP assignment issues, you interact with the NetworkClient and ContainerClient APIs located in the service layer. This guide explains the architecture and provides practical code examples for network migration and debugging.

Network Architecture Overview

The networking stack centers on three primary types defined in the service layer. The NetworkClient class in Sources/Services/ContainerAPIService/Client/NetworkClient.swift serves as the XPC client for creating, listing, and deleting networks. It exposes methods including create(configuration:), list(), get(id:), and delete(id:).

The NetworkConfiguration struct defines the desired state when creating a network, initialized with parameters for name, mode, ipv4Subnet, ipv6Subnet, labels, plugin, and options. The NetworkResource represents the runtime state returned by the server, containing the assigned ipv4Address, ipv6Address, MAC address, and the isBuiltin flag.

Container network attachments are managed through ContainerClient, which provides attachNetwork(containerId:networkId:) and detachNetwork(containerId:networkId:) methods. According to the test references in Tests/CLITests/Utilities/CLITest.swift, attachment information is stored in the container’s status as ContainerResource.Attachment objects.

Migrating Containers Between Networks

To migrate a container to a different network, you must detach it from its current network and attach it to the target. The CLI commands container network disconnect and container network connect are thin wrappers around the XPC routes networkDisconnect and networkConnect.

Identify the Target Network

First, locate the destination network using NetworkClient.list() or create a new one. You can inspect existing networks to verify subnet availability and avoid conflicts.

let client = NetworkClient()
let networks = try await client.list()
for net in networks {
    print("Network: \(net.name) – Subnet: \(net.ipv4Subnet?.description ?? "none")")
}

Detach from the Current Network

Before attaching to a new network, disconnect the container from its existing one. While stopping the container is optional, it ensures reliable detachment. Use ContainerClient.detachNetwork(containerId:networkId:) or the CLI:

container network disconnect my-container default

This sends the networkDisconnect route to the server and updates the container’s networks array in its status record.

Attach to the New Network

Connect the container to the target network using ContainerClient.attachNetwork(containerId:networkId:). The server allocates a fresh IP from the network’s subnet via the container-network-vmnet plugin.

container network connect my-container backend

If using the Swift API directly:

let containerClient = ContainerClient()
try await containerClient.detachNetwork(containerId: "my-container", networkId: "default")
try await containerClient.attachNetwork(containerId: "my-container", networkId: "backend")

Troubleshooting IP Assignment

IP allocation occurs inside the container-network-vmnet plugin, which respects the CIDR supplied in NetworkConfiguration.ipv4Subnet and ipv6Subnet. When debugging IP issues, inspect the NetworkResource using NetworkClient.get(id:) or the CLI container network inspect.

No IP Address Assigned

If container network connect succeeds but no IP appears, verify the network configuration contains a valid CIDR. In Sources/ContainerResource/Network/NetworkConfiguration.swift, ensure you initialized the configuration with a proper subnet string:

let config = try NetworkConfiguration(
    name: "frontend",
    mode: .bridge,
    ipv4Subnet: CIDRv4("10.42.0.0/24"),
    ipv6Subnet: nil,
    labels: .init(),
    plugin: "container-network-vmnet",
    options: [:]
)

IP Conflicts and Duplicate Addresses

Overlapping subnets or stale lease records cause conflicts. List all networks and verify subnets are non-overlapping. If conflicts persist, delete and recreate the network to clear stale state.

Container Shows "none" Network

A container displays none network when started with --network none or when the default network was improperly removed. Check the container status and re-attach using container network connect.

MAC Address Mismatch

Supplying an invalid mac key in the options dictionary of NetworkConfiguration causes assignment failures. Remove manual MAC entries from the options map to let the plugin generate valid addresses automatically.

Practical Implementation Examples

Creating a Custom Network

Define a network with specific CIDR blocks using NetworkConfiguration and NetworkClient:

import ContainerResource
import Services.ContainerAPIService.Client

let client = NetworkClient()

let config = try NetworkConfiguration(
    name: "backend",
    mode: .bridge,
    ipv4Subnet: CIDRv4("10.99.0.0/24"),
    ipv6Subnet: nil,
    labels: .init(),
    plugin: "container-network-vmnet",
    options: [:]
)

let network = try await client.create(configuration: config)
print("Created network \(network.name) with ID \(network.id)")

Inspecting Network Resources

Retrieve runtime details including assigned IPs:

let net = try await client.get(id: "backend")
print("Network \(net.name) – IPv4 range: \(net.ipv4Subnet?.description ?? "none")")
print("Attached containers:")
for container in net.attachedContainers {
    print(" – \(container.id): \(container.ipv4Address?.description ?? "none")")
}

Fixing Missing IP Addresses

When a container lacks an IP, verify attachment and re-connect if necessary:

let containerClient = ContainerClient()
let status = try await containerClient.inspect(id: "my-app")
guard let ip = status.networks.first?.ipv4Address else {
    print("No IPv4 address – re-attaching to network")
    try await containerClient.attachNetwork(containerId: "my-app", networkId: "frontend")
    return
}

Note that the builtin network (default) accessed via NetworkClient.builtin cannot be deleted. If a container is attached to it, you must explicitly disconnect it before connecting to other networks.

Summary

  • Migrate containers by calling ContainerClient.detachNetwork() followed by attachNetwork(), or using the CLI container network disconnect and connect commands.
  • Network configuration is defined in NetworkConfiguration and managed through NetworkClient in Sources/Services/ContainerAPIService/Client/NetworkClient.swift.
  • IP assignment depends on valid CIDR blocks in the network configuration and the container-network-vmnet plugin.
  • Troubleshoot missing IPs by verifying subnet validity, checking for overlaps, and ensuring the options dictionary does not contain invalid MAC addresses.
  • The builtin network cannot be deleted; containers must be explicitly disconnected from it before migrating.

Frequently Asked Questions

Can I migrate a container between networks without stopping it?

Yes, though stopping the container is recommended for reliable detachment. You can execute container network disconnect and container network connect while the container is running, as the ContainerClient methods update the network attachment in the container’s status dynamically.

Why does my container show no IP address after connecting to a network?

This occurs when the NetworkConfiguration lacks a valid CIDR or when the ipv4Subnet parameter is malformed. Verify the network configuration in Sources/ContainerResource/Network/NetworkConfiguration.swift and ensure you passed a valid subnet like CIDRv4("10.0.1.0/24") during creation.

What is the builtin network and can I delete it?

The builtin network (named default) is a special NetworkResource where isBuiltin is true. According to the source code in NetworkClient.swift, this network cannot be deleted. If a container is attached to it, you must explicitly disconnect it using container network disconnect before attaching to other networks.

How do I avoid IP conflicts when creating multiple networks?

Ensure each NetworkConfiguration uses non-overlapping subnets. Check existing networks using NetworkClient.list() to inspect current ipv4Subnet ranges. If conflicts arise from stale lease records, delete and recreate the affected network to clear the plugin state.

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 →