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.tomloverride system defaults. - Schema is defined in
Sources/ContainerPersistence/ContainerSystemConfig.swiftwith 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 andloadForPlugin()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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →