How to Extend Container Functionality with Plugins in the Apple Container Project
You can extend container functionality with plugins by placing a JSON manifest and binary in the plugins directory, which the PluginLoader discovers and the PluginFactory loads into the CLI command registry.
The Apple Container project provides a modular plugin architecture that allows developers to add new commands, services, and runtime helpers without modifying core source code. By leveraging the PluginLoader, PluginConfig, and PluginFactory components, you can extend container functionality with plugins through a simple drop-in mechanism. The system supports both standard binaries and macOS app bundles, making it flexible for different distribution needs.
Understanding the Plugin Architecture
The plugin system operates through three distinct phases: discovery, description, and creation. Each phase is handled by specific components tested in Tests/ContainerPluginTests/.
Plugin Discovery with PluginLoader
The PluginLoader walks a configurable plugins directory, validates each entry, and returns a collection of Plugin objects. According to the test file [PluginLoaderTest.swift](https://github.com/apple/container/blob/main/Tests/ContainerPluginTests/PluginLoaderTest.swift), the loader can filter plugins based on allowed environment variables, ensuring only authorized extensions load in restricted environments.
Plugin Description via PluginConfig
Each plugin ships a JSON or YAML manifest that the system parses into a PluginConfig instance. As demonstrated in [PluginConfigTest.swift](https://github.com/apple/container/blob/main/Tests/ContainerPluginTests/PluginConfigTest.swift), the config defines the plugin's abstract name, author, and the services it provides (such as runtime or cli).
Plugin Creation through PluginFactory
The PluginFactory turns a manifest and binary into a Plugin instance. The default implementation (DefaultPluginFactory) loads binaries from the file system, while AppBundlePluginFactory handles plugins packaged as macOS app bundles. See [PluginFactoryTest.swift](https://github.com/apple/container/blob/main/Tests/ContainerPluginTests/PluginFactoryTest.swift) for implementation details.
Creating a Custom Plugin
To extend container functionality with plugins, you need three components: a manifest file, a compiled binary, and proper installation into the plugins directory.
Step 1: Define the Plugin Manifest
Create a plugin.json file that describes your plugin's capabilities:
{
"abstract": "my-tool",
"author": "Acme Corp",
"servicesConfig": {
"services": [
{
"type": "runtime",
"description": "Provides a custom runtime helper"
}
]
}
}
The PluginConfig decoder parses this manifest to determine which services your plugin exports and how the CLI should register its commands.
Step 2: Build the Plugin Binary
Create a Swift executable and include the manifest as a resource:
# Initialize the Swift package
swift package init --type executable -n MyTool
# Add the manifest as Resources/Plugin.json
swift build -c release
# The resulting binary appears in .build/release/MyTool
Step 3: Install the Plugin
Copy both the binary and manifest to the Container plugins directory:
# Create the plugins directory if it does not exist
mkdir -p ~/.container/plugins
# Copy the binary and its manifest
cp .build/release/MyTool ~/.container/plugins/
cp Resources/Plugin.json ~/.container/plugins/
The PluginLoader discovers this bundle automatically the next time the CLI runs.
Loading Mechanisms
The Container project supports two distinct loading strategies depending on how you package your plugin.
Standard Binary Loading
For most use cases, DefaultPluginFactory loads plugin binaries directly from the file system. This factory reads the manifest, resolves the binary path, and instantiates the Plugin model object that holds the binary URL and parsed configuration. The Plugin model usage is illustrated in [PluginTest.swift](https://github.com/apple/container/blob/main/Tests/ContainerPluginTests/PluginTest.swift).
macOS App Bundle Support
For macOS-specific distributions, AppBundlePluginFactory detects .app bundle layouts and loads the executable automatically:
# Build a macOS app bundle
swift build -c release -Xswiftc -target -Xswiftc x86_64-apple-macosx10.15
# Copy the bundle to the plugins directory
cp -R MyTool.app ~/.container/plugins/
This approach is particularly useful for distributing GUI tools or complex dependencies alongside your container plugin.
CLI Integration
When the Container CLI starts, [ContainerCLI.swift](https://github.com/apple/container/blob/main/Sources/CLI/ContainerCLI.swift) requests active plugins from the PluginLoader, merges their service definitions into the command registry, and executes the requested command using the appropriate plugin binary.
You can verify plugin installation and invoke commands:
# List available plugins (debug helper)
container plugins list
# Run a command provided by the plugin
container my-tool run --option value
The CLI forwards the sub-command to the plugin binary, which receives the remaining arguments unchanged.
Summary
- Three-phase architecture: Discovery (
PluginLoader), description (PluginConfig), and creation (PluginFactory) enable modular extension without core code changes. - Manifest-driven: Each plugin requires a JSON/YAML manifest defining its abstract name, author, and service types.
- Flexible loading: Support for both standard binaries (
DefaultPluginFactory) and macOS app bundles (AppBundlePluginFactory). - Automatic registration: The CLI merges plugin services into the command registry at startup, making new commands immediately available.
Frequently Asked Questions
Where does the PluginLoader search for plugins?
The PluginLoader searches the configurable plugins directory, defaulting to ~/.container/plugins/. As shown in [PluginLoaderTest.swift](https://github.com/apple/container/blob/main/Tests/ContainerPluginTests/PluginLoaderTest.swift), it validates each entry and can filter based on allowed environment variables before returning the collection of valid Plugin objects.
What file format should the plugin manifest use?
The plugin manifest must be valid JSON or YAML that decodes into a PluginConfig structure. The manifest must include the abstract name, author, and a servicesConfig section defining the service types (such as runtime or cli). See [PluginConfigTest.swift](https://github.com/apple/container/blob/main/Tests/ContainerPluginTests/PluginConfigTest.swift) for parsing examples.
Can I distribute Container plugins as macOS app bundles?
Yes. The AppBundlePluginFactory implementation supports loading plugins packaged as .app bundles. Place the entire bundle in the plugins directory, and the factory will automatically detect the bundle layout and load the contained executable. This is tested in [PluginFactoryTest.swift](https://github.com/apple/container/blob/main/Tests/ContainerPluginTests/PluginFactoryTest.swift).
How does the Container CLI know which plugin handles a specific command?
The CLI reads the servicesConfig from each plugin's manifest during startup. When you invoke container my-tool run, the CLI matches my-tool against the abstract name in the plugin configs and forwards the command to the corresponding binary. The plugin binary receives all remaining arguments directly.
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 →