# How to Extend Container Functionality with Plugins in the Apple Container Project

> Extend container functionality with plugins. Learn how to add your own plugins using a JSON manifest and binary in the plugins directory for the apple/container project.

- Repository: [Apple/container](https://github.com/apple/container)
- Tags: how-to-guide
- Published: 2026-06-14

---

**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/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/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/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`](https://github.com/apple/container/blob/main/plugin.json) file that describes your plugin's capabilities:

```json
{
  "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:

```bash

# 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:

```bash

# 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/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:

```bash

# 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/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:

```bash

# 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/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/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/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.