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-ipis optional and defaults to0.0.0.0(all interfaces).protocolis optional and defaults totcp, but can be set toudp.
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– eitherPublishProtocol.tcpor.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:
- Validation: Checks that the list is non-empty and that no two specifications overlap using
publishedPorts.hasOverlaps(). - Address Creation: For each
PublishPort, creates aSocketAddressfor the host proxy (proxyAddress) and a matching address on the container side (serverAddress). - Forwarder Selection: Chooses between
TCPForwarderandUDPForwarder(lines 12-20) based on theprotofield.
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
-pwith the format[host-ip:]host-port:container-port[/protocol], stored in thePublishPortstruct and validated byPublishPort.validatePortRange. - Runtime forwarding is handled by
RuntimeService.startSocketForwarders, which createsTCPForwarderorUDPForwarderinstances based on the protocol. - Socket publishing uses
--publish-socketwith the formathost_path:container_path, creatingPublishSocketentries that are mounted into the container filesystem. - All specifications are parsed by
Parser.swiftand 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →