How to Configure Network Socket Forwarding and Port Publishing in apple/container

Use the -p or --publish flag for TCP/UDP port mapping and --publish-socket for Unix-domain sockets, parsed by Parser.swift and executed by RuntimeService.startSocketForwarders.

The apple/container runtime implements network socket forwarding and port publishing as first-class features, allowing you to expose container services on host interfaces or share Unix sockets between the host and container filesystems. This guide explains the configuration syntax, underlying data models, and runtime implementation based on the actual source code.

Port Publishing with -p and --publish

The -p or --publish flag maps host IP addresses and ports to container ports using the format:


[host-ip:]host-port:container-port[/protocol]

  • host-ip is optional and defaults to 0.0.0.0 (all interfaces).
  • protocol is optional and defaults to tcp, but can be set to udp.

The PublishPort Data Model

When the CLI parses your command, Parser.publishPorts (located in Sources/Services/ContainerAPIService/Client/Parser.swift at lines 11-18) constructs a PublishPort struct defined in Sources/ContainerResource/Container/PublishPort.swift. This struct stores:

  • hostAddress – the IP address on the host side.
  • hostPort – the starting host port (UInt16).
  • containerPort – the target port inside the container.
  • proto – either PublishProtocol.tcp or .udp.
  • count – the number of consecutive ports to forward (defaults to 1).

The PublishPort.validatePortRange method (lines 88-91) ensures that hostPort + count - 1 does not overflow UInt16, preventing invalid port ranges from reaching the runtime.

Runtime Socket Forwarding

When a container starts, RuntimeService receives the list of PublishPort objects via config.publishedPorts and initiates the forwarding logic.

How RuntimeService.startSocketForwarders Works

The RuntimeService.startSocketForwarders method in Sources/Services/RuntimeLinux/Server/RuntimeService.swift (lines 776-848) performs three critical steps:

  1. Validation: Checks that the list is non-empty and that no two specifications overlap using publishedPorts.hasOverlaps().
  2. Address Creation: For each PublishPort, creates a SocketAddress for the host proxy (proxyAddress) and a matching address on the container side (serverAddress).
  3. Forwarder Selection: Chooses between TCPForwarder and UDPForwarder (lines 12-20) based on the proto field.

The forwarder implementations in Sources/SocketForwarder/TCPForwarder.swift and Sources/SocketForwarder/UDPForwarder.swift use SwiftNIO to bind a listener on the host and proxy traffic to the container’s network namespace. If you attempt to bind a privileged port (below 1024) without sufficient permissions, the runtime translates the error into a clear ContainerizationError (lines 28-34).

Unix-Domain Socket Publishing with --publish-socket

For Unix-domain sockets, use the --publish-socket flag with the format:


host_path:container_path

Socket Specification and Validation

Parser.publishSocket in Sources/Services/ContainerAPIService/Client/Parser.swift (lines 38-94) validates the host path, ensures the target is a Unix-domain socket (or creates the parent directory), and returns a PublishSocket struct defined in Sources/ContainerResource/Container/PublishSocket.swift. This struct captures the source and destination paths for mounting.

Mount Integration

During container creation, the ContainerConfiguration.publishedSockets array is populated. The runtime then mounts the socket file into the container’s filesystem using the same path resolution logic as the --volume implementation, ensuring the container can communicate with host services like the Docker daemon.

Practical Configuration Examples

Publish a Single TCP Port

Forward host 127.0.0.1:8080 to container port 80:

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

Publish a UDP Port Range

Forward host ports 9000-9004 to container ports 8000-8004 via UDP:

container run -d --publish 0.0.0.0:9000-9004:8000-8004/udp my-app

The count field becomes 5, spawning five parallel UDPForwarder instances.

Publish a Unix-Domain Socket

Expose the host Docker socket inside the container:

container run -d \
    --publish-socket /var/run/docker.sock:/var/run/docker.sock \
    --volume /var/run/docker.sock:/var/run/docker.sock \
    my-builder

Combine Ports and Sockets

Multiple publish flags can be combined in a single command:

container run -d \
    -p 8080:80 \
    -p 8443:443/tcp \
    -p 9000-9002:8000-8002/udp \
    --publish-socket /tmp/my.sock:/var/run/my.sock \
    my-app

Summary

  • Port publishing uses -p with the format [host-ip:]host-port:container-port[/protocol], stored in the PublishPort struct and validated by PublishPort.validatePortRange.
  • Runtime forwarding is handled by RuntimeService.startSocketForwarders, which creates TCPForwarder or UDPForwarder instances based on the protocol.
  • Socket publishing uses --publish-socket with the format host_path:container_path, creating PublishSocket entries that are mounted into the container filesystem.
  • All specifications are parsed by Parser.swift and processed independently by the runtime.

Frequently Asked Questions

What is the difference between port publishing and socket publishing?

Port publishing maps TCP or UDP ports between the host and container network namespaces, enabling external access to containerized services. Socket publishing binds a Unix-domain socket from the host into the container filesystem, allowing inter-process communication with host services like the Docker daemon.

Can I publish a range of ports in a single command?

Yes. The PublishPort struct includes a count field that specifies how many consecutive ports to forward. The CLI syntax uses a hyphen (e.g., 9000-9004:8000-8004), and the runtime spawns separate forwarder instances for each port in the range.

How does the runtime handle port conflicts?

Before starting forwarders, RuntimeService.startSocketForwarders calls publishedPorts.hasOverlaps() to detect duplicate or overlapping port specifications. If conflicts are found, the container fails to start with a validation error.

Why am I getting an error when binding to ports below 1024?

Binding to privileged ports (below 1024) requires root privileges. If the runtime lacks sufficient permissions, TCPForwarder or UDPForwarder raises an error that is translated into a descriptive ContainerizationError (lines 28-34 of the forwarder implementation). Run the command with elevated privileges or choose a higher port number.

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 →