# How Container Integrates with Launchd for Service Management

> Learn how container integrates with Launchd for macOS service management. Discover how it uses launchctl and property lists for native process control.

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

---

**Container integrates with Launchd by wrapping `launchctl` commands in a `ServiceManager` struct that registers XML property lists generated by the `LaunchPlist` model, enabling native macOS process lifecycle management.**

The Apple Container project delegates all service supervision to macOS's native `launchd` system, avoiding custom process monitoring in favor of the platform's authoritative service manager. This architecture ensures that the Container API server, plugins, and helper processes benefit from Launchd's automatic restart, logging, and security sandboxing capabilities. The integration centers on two Swift components that bridge high-level container operations with low-level `launchctl` commands.

## Core Integration Components

### ServiceManager

The `ServiceManager` struct in [`Sources/ContainerPlugin/ServiceManager.swift`](https://github.com/apple/container/blob/main/Sources/ContainerPlugin/ServiceManager.swift) serves as a thin Swift wrapper around the `launchctl` command-line tool. Rather than interfacing with Launchd directly via XPC, it abstracts common service lifecycle actions by spawning `Process` instances pointing at `/bin/launchctl`.

This wrapper implements five critical operations:
- **Bootstrap** (`register`): Loads a property list into Launchd via `launchctl bootstrap <domain> <plistPath>`.
- **Bootout** (`deregister`): Stops and unloads a service using `launchctl bootout <label>`.
- **Kickstart** (`kickstart`): Restarts a running service with `launchctl kickstart -k <label>`.
- **Kill** (`kill`): Sends signals to processes using `launchctl kill <signal> <label>`.
- **Enumeration** (`enumerate`): Parses the output of `launchctl list` to return loaded service labels.

The struct also provides `getDomainString()`, which queries `launchctl managername` to determine the current session type—returning `system`, `gui/<uid>`, or `user/<uid>`—ensuring services load in the correct security context.

### LaunchPlist

The `LaunchPlist` struct in [`Sources/ContainerPlugin/LaunchPlist.swift`](https://github.com/apple/container/blob/main/Sources/ContainerPlugin/LaunchPlist.swift) models the XML property list files that Launchd consumes. It conforms to `Encodable` and provides an `encode()` method that generates valid XML plist documents ready for the `bootstrap` command.

Key properties mapped to Launchd keys include:
- `Label`: The unique service identifier.
- `ProgramArguments`: Array of executable path and arguments.
- `EnvironmentVariables`: Dictionary of environment values passed to the service.
- `RunAtLoad`: Boolean triggering immediate startup after bootstrap.
- `MachServices`: Dictionary enabling XPC communication between Container components.
- `LimitLoadToSessionType`: Constraints on which user sessions may load the service.

The struct also supports debugging via the `CONTAINER_DEBUG_LAUNCHD_LABEL` environment variable, which sets the `waitForDebugger` flag in the generated plist, causing Launchd to pause service startup until a debugger attaches.

## Service Lifecycle Workflow

Container manages services through a six-step workflow that maps directly to Launchd primitives:

1. **Determine Launchd Domain**  
   `ServiceManager.getDomainString()` (lines 24-36 in [`ServiceManager.swift`](https://github.com/apple/container/blob/main/ServiceManager.swift)) executes `launchctl managername` to detect whether the current context is `System`, `Aqua`, or `Background`, then returns the appropriate domain identifier for subsequent commands.

2. **Generate Property List**  
   When starting a service (e.g., via [`Sources/ContainerCommands/System/SystemStart.swift`](https://github.com/apple/container/blob/main/Sources/ContainerCommands/System/SystemStart.swift)), the code constructs a `LaunchPlist` instance defining the service label, executable path, and runtime options like `keepAlive` or `machServices`. The `encode()` method writes this data to `/var/run/container/launchd/<label>.plist`.

3. **Register with Launchd**  
   `ServiceManager.register(plistPath:)` invokes `launchctl bootstrap <domain> <plistPath>`, instructing Launchd to load the configuration and begin monitoring the service.

4. **Control Service State**  
   Container sends lifecycle commands through `ServiceManager`:
   - *Restart*: `kickstart(label:)` calls `launchctl kickstart -k <label>`.
   - *Stop*: `deregister(fullServiceLabel:)` calls `launchctl bootout <label>`.
   - *Signal*: `kill(fullServiceLabel:, signal:)` calls `launchctl kill <signal> <label>`.

5. **Query Status**  
   `ServiceManager.enumerate()` runs `launchctl list` and parses the third column to return active service labels. `isRegistered(fullServiceLabel:)` checks specific label status via `launchctl list <label>`.

6. **Cleanup on Shutdown**  
   The [`scripts/ensure-container-stopped.sh`](https://github.com/apple/container/blob/main/scripts/ensure-container-stopped.sh) script uses `launchctl managername` to discover the current domain before iterating through container services and executing `bootout` to ensure clean termination.

## Practical Implementation Example

The following Swift example demonstrates registering a custom container service through Launchd:

```swift
import Foundation
import ContainerPlugin

// Configure the Launchd property list
let plist = LaunchPlist(
    label: "com.example.container.myservice",
    arguments: ["/usr/local/bin/myservice", "--port", "8080"],
    runAtLoad: true,
    keepAlive: true,
    machServices: ["com.example.container.myservice"]
)

// Write the plist to the Launchd directory
let plistData = try plist.encode()
let plistPath = "/var/run/container/launchd/com.example.container.myservice.plist"
try plistData.write(to: URL(fileURLWithPath: plistPath))

// Register with the appropriate Launchd domain
try ServiceManager.register(plistPath: plistPath)

// Restart the service later if needed
let domain = try ServiceManager.getDomainString()
try ServiceManager.kickstart(fullServiceLabel: "\(domain)/com.example.container.myservice")

```

High-level CLI commands in [`Sources/ContainerCommands/System/SystemStart.swift`](https://github.com/apple/container/blob/main/Sources/ContainerCommands/System/SystemStart.swift) and [`SystemStop.swift`](https://github.com/apple/container/blob/main/SystemStop.swift) wrap these operations, allowing users to execute `container start` and `container stop` without manually interacting with `launchctl`.

## Summary

- Container treats Launchd as the single source of truth for process lifecycle, using `ServiceManager` to wrap `launchctl` commands for bootstrap, bootout, andKill operations.
- The `LaunchPlist` struct in [`Sources/ContainerPlugin/LaunchPlist.swift`](https://github.com/apple/container/blob/main/Sources/ContainerPlugin/LaunchPlist.swift) type-safely generates XML property lists with support for `MachServices`, environment variables, and debugging flags.
- Domain detection via `launchctl managername` ensures services load in the correct security context (system, GUI, or user).
- Services are registered by writing plists to `/var/run/container/launchd/` and calling `launchctl bootstrap`, enabling automatic restart and monitoring without custom watchdog code.

## Frequently Asked Questions

### How does Container determine which Launchd domain to use for service registration?

Container calls `launchctl managername` through `ServiceManager.getDomainString()` to detect the current session type. This returns `system` for system-wide daemons, `gui/<uid>` for user sessions connected to the Aqua window server, or `user/<uid>` for background sessions, ensuring services run with appropriate permissions and visibility.

### What is the difference between `bootstrap` and `kickstart` in Container's Launchd integration?

`bootstrap` (via `ServiceManager.register`) performs the initial registration of a property list with Launchd, creating the service entry and optionally starting it if `RunAtLoad` is true. `kickstart` (via `ServiceManager.kickstart`) forces an immediate restart of an already registered service, useful for reloading configurations without full deregistration.

### Can Container services communicate via XPC, and how is this configured?

Yes, the `LaunchPlist` struct includes a `machServices` dictionary that populates the `MachServices` key in the generated plist. This enables XPC communication between the Container API server and its helper processes, with the service manager using these labels to route inter-process messages.

### How can developers debug Container services that are managed by Launchd?

Set the `CONTAINER_DEBUG_LAUNCHD_LABEL` environment variable before starting the service. When present, `LaunchPlist` sets the `waitForDebugger` flag in the generated property list, causing Launchd to pause the service process immediately after startup until a debugger attaches via the specified Mach service label.