How to Use the Plugin System for Network and Runtime Extensions in apple/container
The apple/container plugin system discovers external binaries from host directories, instantiates them as Swift structs conforming to the Plugin protocol, and registers them with launchd to provide network or runtime services on demand without modifying core container code.
The primary keyword is "plugin system for network and runtime extensions in apple/container". This architecture allows developers to extend the macOS container runtime with custom networking backends and alternative execution environments by placing structured bundles in specific filesystem locations.
Core Plugin Architecture
The plugin system centers on five key components that handle discovery, configuration, and lifecycle management.
The Plugin Struct
At Sources/ContainerPlugin/Plugin.swift, the Plugin struct represents a single extension. It encapsulates the plugin's name, binary path, launchd label, and service capabilities (runtime, network, or both). The struct conforms to the Plugin protocol and provides methods like getLaunchdLabel() which returns the standardized label com.apple.container.<name>.
Configuration and Loading
PluginConfig (Sources/ContainerPlugin/PluginConfig.swift) decodes each plugin's plugin.json configuration file. This JSON schema defines the service types, default arguments, and boot behavior. PluginLoader (Sources/ContainerPlugin/PluginLoader.swift) scans designated directories, validates bundle integrity, and instantiates Plugin objects. It automatically registers valid plugins with launchd using PluginLoader.registerWithLaunchd.
State Management and Runtime Integration
PluginStateRoot (Sources/ContainerPlugin/PluginStateRoot.swift) provisions per-instance state directories for logs and sockets. At runtime, PluginsHarness (Sources/Services/ContainerAPIService/Server/Plugin/PluginsHarness.swift) aggregates all loaded plugins and routes container API requests to the appropriate extension via Mach services.
Creating a Custom Plugin
Adding a new network or runtime extension requires a specific bundle structure and JSON configuration.
Bundle Structure
Create a directory with the following layout:
my-runtime/
├── bin/ # executable that implements the service
│ └── my-runtime
└── plugin.json # configuration file
The binary must reside in the bin/ subdirectory, and the directory name determines the plugin identifier.
Configuring plugin.json
The configuration file must match the schema parsed by PluginConfig. A minimal runtime plugin configuration looks like this:
{
"type": ["runtime"],
"defaultArguments": ["--listen", "unix:///var/run/my-runtime.sock"],
"shouldBoot": false
}
Key fields include:
- type: Array containing
"runtime","network", or both - defaultArguments: Array of strings passed to the binary on startup
- shouldBoot: Boolean indicating whether launchd should start the service at boot (
true) or on-demand (false)
Installation and Registration
Place the bundle in one of the scanned directories:
- System-wide:
/usr/local/libexec/container/plugin/ - Per-user:
$HOME/.local/libexec/container/plugin/ - Custom: Any path specified via the
--plugin-dirCLI flag
When the container runtime initializes, PluginLoader discovers the bundle, validates the executable, and registers the plugin with launchd using the label convention com.apple.container.<name>. The test suite in Tests/ContainerPluginTests/PluginLoaderTest.swift verifies this discovery and registration workflow.
Using Network Extensions
Network plugins declare the "network" type in their plugin.json and expose a Mach service at com.apple.container.network.<name>.
Service Discovery and CLI Usage
List available network plugins:
container network list
Connect a container to a custom network backend:
container network connect --plugin my-network-plugin <container-id>
The plugin receives the request via its Mach service, creates the virtual network interface (often backed by vmnet), and returns the endpoint to the container runtime. The plugin binary handles the actual network setup while the core container code manages the integration.
Using Runtime Extensions
Runtime plugins provide alternative execution environments such as custom sandboxes. They declare "runtime" in the type array of plugin.json.
Invoke a container with a specific runtime plugin:
container run --runtime sandboxd alpine:latest
The runtime passes the container's launch parameters to the plugin binary, which sets up the sandbox environment before execing the container process. This allows integration with macOS security frameworks without modifying the containerd-style core runtime.
Complete Example: Hydra Plugin
The repository's test fixtures include a reference implementation. The hydra plugin bundle demonstrates a dual-service extension:
hydra/
├── bin/
│ └── hydra
└── plugin.json
With plugin.json configured as:
{
"type": ["runtime", "network"],
"defaultArguments": ["start", "with", "params"],
"shouldBoot": true
}
This configuration registers two Mach services: com.apple.container.runtime.hydra and com.apple.container.network.hydra. The shouldBoot: true setting ensures launchd starts the service at boot time rather than on-demand. The test file Tests/ContainerPluginTests/PluginTest.swift validates that the Plugin struct correctly generates launchd labels and Mach service names for this bundle.
Summary
- Plugin discovery occurs via
PluginLoaderscanning/usr/local/libexec/container/plugin/and user directories, or paths specified with--plugin-dir. - Bundle requirements include a
bin/directory containing the executable and aplugin.jsonfile defining the service type and arguments. - Registration happens automatically through
PluginLoader.registerWithLaunchd, creating launchd plists with labels following thecom.apple.container.<name>convention. - Network plugins expose Mach services at
com.apple.container.network.<name>and integrate withcontainer networkcommands. - Runtime plugins activate via the
--runtimeflag and handle container execution environment setup. - State persistence is managed through
PluginStateRoot, providing per-instance directories for logs and sockets.
Frequently Asked Questions
How does apple/container discover new plugins?
The PluginLoader class recursively scans predefined directories and any paths specified via the --plugin-dir CLI flag. It validates each directory containing a plugin.json file and an executable in bin/, then instantiates a Plugin struct. Discovery is tested in Tests/ContainerPluginTests/PluginLoaderTest.swift.
What is the difference between runtime and network plugin types?
Runtime plugins provide alternative execution environments and are invoked via the --runtime flag when starting containers. Network plugins manage virtual network interfaces and are accessed through container network commands. A single plugin can declare both types in the type array of plugin.json to provide hybrid services.
How does the plugin system integrate with launchd?
PluginLoader automatically generates launchd property lists for each discovered plugin using the label format com.apple.container.<name>. If shouldBoot is true in plugin.json, launchd starts the service at system boot; otherwise, it uses launchd's on-demand activation when the Mach service is first accessed.
Can plugins store persistent state?
Yes. The PluginStateRoot class provides each plugin instance with a dedicated state directory for storing logs, sockets, and other persistent data. This directory is managed separately from the plugin bundle and persists across container restarts.
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 →