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-dir CLI 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 PluginLoader scanning /usr/local/libexec/container/plugin/ and user directories, or paths specified with --plugin-dir.
  • Bundle requirements include a bin/ directory containing the executable and a plugin.json file defining the service type and arguments.
  • Registration happens automatically through PluginLoader.registerWithLaunchd, creating launchd plists with labels following the com.apple.container.<name> convention.
  • Network plugins expose Mach services at com.apple.container.network.<name> and integrate with container network commands.
  • Runtime plugins activate via the --runtime flag 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:

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 →