How to Configure System Properties Using config.toml in Apple Container

Apple Container reads system properties from a layered set of config.toml files, merging user settings in ~/.config/container/config.toml with system defaults and applying them when the daemon starts.

The Apple Container project provides a flexible configuration system that allows you to customize VM resources, DNS settings, registry endpoints, and plugin behavior through TOML files. Understanding how to configure system properties using config.toml in Container enables you to tune the runtime environment without modifying source code. This guide explains the configuration schema, file precedence, and practical workflows based on the actual implementation in the apple/container repository.

Configuration File Locations and Precedence

The ConfigurationLoader implemented in Sources/ContainerPersistence/ConfigurationLoader.swift searches for config.toml in three distinct locations, applying a first-match-wins precedence order:

Layer Path Role
User ~/.config/container/config.toml Editable by the logged-in user; highest priority.
App-root <app-root>/config/config.toml Read-only snapshot that the daemon uses at runtime.
Install-root /usr/local/etc/container/config.toml System-wide defaults shipped with the installer.

When ConfigurationLoader.load() executes, it builds a list of FileProvider<TOMLSnapshot> instances for each layer, merges them into a single ConfigSnapshot, and decodes the result into ContainerSystemConfig. Values defined in the user file shadow those in the app-root and install-root files.

Configuration Schema and Structure

The TOML schema is defined by the ContainerSystemConfig class in Sources/ContainerPersistence/ContainerSystemConfig.swift. Each top-level table maps to a specific nested struct:

  • [build]BuildConfig (builder VM resources)
  • [container]ContainerConfig (default per-container resources)
  • [dns]DNSConfig (DNS domain settings)
  • [kernel]KernelConfig (kernel parameters)
  • [network]NetworkConfig (networking options)
  • [registry]RegistryConfig (default registry endpoint)
  • [vminit]VminitConfig (initialization settings)
  • [plugin.<id>] → Plugin-specific configurations

All sections are optional; omitted keys fall back to the hard-coded defaults defined in the struct initializers. The file must be UTF-8 encoded and use standard TOML table syntax as documented in docs/container-system-config.md.

How to Edit and Apply Configuration

Step 1: Create the User Configuration File

Create and edit the user-level configuration file using standard shell commands:

mkdir -p ~/.config/container
touch ~/.config/container/config.toml
$EDITOR ~/.config/container/config.toml

Step 2: Define Your Properties

Add only the tables and keys you wish to override. For example, to disable Rosetta translation and adjust resource limits:

[build]
rosetta = false
cpus = 4
memory = "4096mb"

[container]
cpus = 2
memory = "2g"

[dns]
domain = "test"

[registry]
domain = "my-registry.example.com"

In this example, the [build] table overrides builder VM resources, [container] changes default per-container allocations, [dns] appends .test to hostnames, and [registry] sets the default registry for unqualified image names.

Step 3: Apply the Configuration

When you run container system start, the daemon automatically copies your user configuration to the app-root location via ConfigurationLoader.copyConfigurationToReadOnly, making it read-only for runtime use. If the daemon is already running, restart it to reload the snapshot:

container system restart

Loading Configuration Programmatically

For Swift applications interacting with the container system, access the merged configuration using the ConfigurationLoader class:

import ContainerPersistence
import SystemPackage

// Load the merged configuration (user + system defaults)
let systemConfig = try await ConfigurationLoader.load()

print("Builder image: \(systemConfig.build.image)")
print("Default container CPUs: \(systemConfig.container.cpus)")
print("DNS domain: \(systemConfig.dns.domain ?? "none")")

The load() method returns a fully populated ContainerSystemConfig instance with merged values from all three layers. You do not need to specify file paths; the loader resolves locations automatically.

Plugin-Specific Configuration

Plugins can define custom configuration tables using the [plugin.<id>] syntax. For example, to configure a runtime plugin:

[plugin.runtime]
logLevel = "debug"
maxConcurrent = 4

Access plugin configuration in Swift by implementing the LoadablePluginConfiguration protocol:

struct RuntimePluginConfig: LoadablePluginConfiguration {
    static let pluginId = "runtime"
    let logLevel: String
    let maxConcurrent: Int

    init() { 
        self.logLevel = "info" 
        self.maxConcurrent = 1 
    }
}

let runtimeConfig = try await ConfigurationLoader.loadForPlugin(RuntimePluginConfig.self)

Summary

  • Apple Container uses a three-layer TOML configuration system with files in user, app-root, and install-root directories.
  • Precedence follows first-match-wins: user settings in ~/.config/container/config.toml override system defaults.
  • Schema is defined in Sources/ContainerPersistence/ContainerSystemConfig.swift with optional tables for build, container, DNS, network, registry, and plugin settings.
  • Workflow involves editing the user config, then starting or restarting the daemon to apply changes via ConfigurationLoader.copyConfigurationToReadOnly.
  • Programmatic access uses ConfigurationLoader.load() for system config and loadForPlugin() for plugin-specific settings.

Frequently Asked Questions

Where does Container look for config.toml files?

Container searches three locations in order: ~/.config/container/config.toml (user), <app-root>/config/config.toml (app-root), and /usr/local/etc/container/config.toml (install-root). According to the ConfigurationLoader implementation in Sources/ContainerPersistence/ConfigurationLoader.swift, the loader merges these files using a first-match-wins strategy, where user values take precedence over system defaults.

Do I need to specify every configuration option in config.toml?

No. All sections in config.toml are optional. You only need to include the specific tables and keys you want to override. Missing values automatically fall back to the hard-coded defaults defined in the ContainerSystemConfig struct initializers, as implemented in Sources/ContainerPersistence/ContainerSystemConfig.swift.

How do I apply changes after editing config.toml?

Run container system restart to reload the configuration. Alternatively, if the daemon is not running, container system start automatically copies your user configuration to the read-only app-root location via ConfigurationLoader.copyConfigurationToReadOnly and loads the new settings.

Can I configure custom properties for Container plugins?

Yes. Use the [plugin.<id>] table syntax in your config.toml file, then access these values in Swift by implementing the LoadablePluginConfiguration protocol and calling ConfigurationLoader.loadForPlugin(). See Sources/Plugins/RuntimeLinux/config.toml for an example plugin configuration file.

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 →