How to Manage the Container Lifecycle with Apple's Container CLI: Create, Start, Stop, and Delete
The container command-line tool provides dedicated subcommands—create, start, stop, and delete—that orchestrate the complete lifecycle of container-based virtual machines on macOS, from initial metadata instantiation through resource cleanup.
The apple/container repository ships with a native macOS container runtime that leverages the Virtualization framework to run lightweight Linux VMs. Understanding how to manage the container lifecycle is essential for developers automating CI pipelines, scripting development environments, or running isolated workloads on Apple silicon.
Creating a Container
The container create Command
Use container create [<options>] <image> [<args> …] to instantiate a stopped container from an OCI image. This command only writes the container’s metadata and prepares the VM; the process does not run yet.
According to the source code in docs/command-reference.md, the CLI parses arguments and forwards the request to the container-runtime-linux XPC helper. Internally, this helper writes a ContainerSpec to the local content store managed by the container-apiserver service.
# Create a stopped container named "my-app" from Ubuntu
container create --name my-app ubuntu:latest
Starting a Container
The container start Command
The container start [--attach] [--interactive] [--debug] <container-id> command boots a previously created container. When --attach is provided, the runtime streams the VM’s console to your terminal.
As implemented in Sources/Services/Runtime/RuntimeClient/RuntimeKeys.swift, the start logic calls RuntimeClient.start(containerID:). The runtime loads the spec, creates the virtual machine via the macOS Virtualization framework, and begins execution of the init process.
# Start the container and attach to its console
container start --attach my-app
Stopping a Container
The container stop Command
To gracefully shut down a running VM, use container stop <container-id>. The implementation sends a SIGTERM to the init process, halts the VM instance via the Virtualization framework, and preserves the container metadata for later reuse.
The stop flow is defined in the same runtime client (RuntimeClient.stop(containerID:)) and communicates with the container-runtime-linux helper through XPC to ensure clean termination.
# Gracefully stop the container
container stop my-app
For automation scenarios, the repository includes scripts/ensure-container-stopped.sh, a helper utility used by CI pipelines to guarantee a clean environment before tests by forcibly ensuring containers reach a stopped state.
Deleting a Container
The container delete Command
The container delete <container-id> command removes all persisted state for the container, including metadata, root-fs snapshots, and attached volumes. After deletion, the container ID can be reused.
Deletion is performed by the ContainerAPIService (accessible via Sources/Services/ContainerAPIService/Client/XPC+.swift), which wipes the container directory under $HOME/.container/containers.
# Permanently remove the container and its resources
container delete my-app
Complete Lifecycle Workflow Examples
Bash Automation Script
For CI/CD pipelines, chain the lifecycle commands to ensure clean state:
#!/usr/bin/env bash
set -euo pipefail
# Clean up any existing container
container stop my-app || true
container delete my-app || true
# Create and start fresh
container create --name my-app ubuntu:latest
container start --attach my-app
Swift Programmatic API
You can also manage the lifecycle directly via Swift using the XPC client:
import ContainerAPIService
let client = ContainerAPIService.Client()
// 1. Create
try client.create(image: "ubuntu:latest", name: "my-app")
// 2. Start
try client.start(containerID: "my-app")
// ... perform work ...
// 3. Stop
try client.stop(containerID: "my-app")
// 4. Delete
try client.delete(containerID: "my-app")
The Swift client mirrors the CLI subcommands and ultimately communicates with the same XPC services (container-runtime-linux and container-apiserver) that the container binary uses.
Key Implementation Files
| File | Purpose |
|---|---|
docs/command-reference.md |
Specification of container create, start, stop, and delete arguments |
Sources/Services/Runtime/RuntimeClient/RuntimeKeys.swift |
Runtime constants driving the start and stop behavior via RuntimeClient |
Sources/Services/ContainerAPIService/Client/XPC+.swift |
XPC client implementation forwarding delete requests to container-apiserver |
scripts/ensure-container-stopped.sh |
CI helper ensuring containers are stopped before test runs |
Tests/IntegrationTests/Utilities/ContainerFixture+ContainerHelpers.swift |
Test harness programmatically driving the lifecycle for verification |
Summary
- Creation (
container create) writes aContainerSpecvia the XPC helper but leaves the VM in a stopped state. - Starting (
container start) invokesRuntimeClient.start(containerID:)to boot the VM using Apple's Virtualization framework. - Stopping (
container stop) sends SIGTERM to the init process and halts the VM while preserving metadata. - Deletion (
container delete) triggersContainerAPIServiceto remove all data from$HOME/.container/containers. - While
container runcombines creation and startup, explicit lifecycle management provides finer-grained control for automation and resource inspection.
Frequently Asked Questions
What is the difference between container run and container create?
container run combines creation and immediate startup into a single command, whereas container create only instantiates the metadata and prepares the VM without booting it. Using create → start allows you to inspect container state, modify resources, or inject custom init scripts before execution begins.
Where does the container runtime store persistent data?
The runtime stores container metadata and root-fs snapshots in the $HOME/.container/containers directory, as managed by the container-apiserver service. When you invoke container delete, the ContainerAPIService wipes this directory for the specific container ID.
How does the container stop command ensure graceful shutdown?
The stop command sends a SIGTERM signal to the container's init process and instructs the Virtualization framework to halt the VM. This flow is implemented in RuntimeClient.stop(containerID:) and communicates via XPC to the container-runtime-linux helper, ensuring the VM terminates cleanly while preserving the container's configuration for potential restarts.
Can I manage containers programmatically instead of using the CLI?
Yes. The ContainerAPIService.Client() Swift class exposed in Sources/Services/ContainerAPIService/Client/XPC+.swift provides create(), start(), stop(), and delete() methods that mirror the CLI. These methods communicate with the same XPC services, making them suitable for macOS applications that need to embed container lifecycle management.
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 →