How to Push and Pull Container Images from Registries with Apple's Container Tool
Use container image push <ref> and container image pull <ref> to transfer images between local storage and OCI registries, with automatic credential handling from the macOS keychain and support for multi-platform images.
The container CLI from Apple provides native support for working with OCI-compatible registries on macOS. Whether you need to distribute applications or consume base images, understanding how to push and pull container images from registries is essential for container-based workflows. This Swift-based tool implements these operations as first-class subcommands with integrated authentication, platform selection, and progress reporting.
Architecture of Push and Pull Operations
According to the apple/container source code, the CLI implements push and pull through dedicated Swift structs located in Sources/ContainerCommands/Image/. The ImagePush struct handles uploads in ImagePush.swift, while ImagePull manages downloads in ImagePull.swift. Both commands follow an identical seven-step execution pipeline that handles configuration loading, platform resolution, and streaming progress updates.
The Seven-Step Execution Flow
When you invoke container image push or container image pull, the tool executes the following sequence:
1. Load Container System Configuration
The CLI first loads your system settings via Application.loadContainerSystemConfig(). This call retrieves your default registry domain, keychain credentials, and TLS configuration settings. According to the source at ImagePull.swift#L71, this configuration determines how the client authenticates to private registries.
let containerSystemConfig = try await Application.loadContainerSystemConfig()
2. Resolve Target Platform
The tool resolves the target platform using DefaultPlatform.resolve(), which processes the --os, --arch, or --platform flags. If you omit these flags, the tool reads the CONTAINER_DEFAULT_PLATFORM environment variable. This logic is shared between both commands, appearing at ImagePull.swift#L73 and ImagePush.swift#L61.
let p = try DefaultPlatform.resolve(platform: platform, os: os, arch: arch, log: log)
3. Select Request Scheme
The --scheme flag determines the transport protocol. The CLI instantiates a RequestScheme to enforce http, https, or auto-detection behavior. This occurs at ImagePush.swift#L63 for push and ImagePull.swift#L75 for pull.
let scheme = try RequestScheme(registry.scheme)
4. Locate the Image Reference
For pull operations, the CLI normalizes the reference string using ClientImage.normalizeReference() at ImagePull.swift#L77. For push operations, it retrieves the local image via ClientImage.get() at ImagePush.swift#L64.
// Pull
let processedReference = try ClientImage.normalizeReference(reference, containerSystemConfig: containerSystemConfig)
// Push
let image = try await ClientImage.get(reference: reference, containerSystemConfig: containerSystemConfig)
5. Initialize Progress Reporting
Both commands instantiate a ProgressBar configured via the --progress flag. Pull operations split progress into fetch and unpack phases, while push operations track blob uploads. See the initialization at ImagePull.swift#L86-L90 and ImagePush.swift#L74-L78.
let progress = ProgressBar(config: progressConfig)
progress.start()
6. Execute the Operation
For pull, the CLI calls ClientImage.pull() with a ProgressTaskCoordinator to handle concurrent downloads. For push, it invokes image.push() to stream layers to the registry. The pull implementation at ImagePull.swift#L96-L100 supports the maxConcurrentDownloads parameter from imageFetchFlags.
// Pull
let image = try await ClientImage.pull(
reference: processedReference,
platform: p,
scheme: scheme,
containerSystemConfig: containerSystemConfig,
progressUpdate: ProgressTaskCoordinator.handler(for: fetchTask, from: progress.handler),
maxConcurrentDownloads: self.imageFetchFlags.maxConcurrentDownloads
)
// Push
_ = try await image.push(
platform: p,
scheme: scheme,
containerSystemConfig: containerSystemConfig,
progressUpdate: progress.handler
)
7. Finalize Output
After completion, the progress bar finishes and the CLI prints the final image reference (push) or unpacks the image layers (pull).
Command-Line Usage Examples
Pushing to a Private Registry
First authenticate using the keychain, then tag and push your image:
# Store credentials in macOS keychain
container registry login my-registry.example.com
# Tag with fully-qualified name
container image tag my-app my-registry.example.com/jdoe/my-app:latest
# Push to registry
container image push my-registry.example.com/jdoe/my-app:latest
Pulling with Platform Selection
Pull the default platform for your host, or specify a target architecture:
# Pull default platform (host OS/arch)
container image pull alpine:latest
# Pull specific platform (e.g., Linux arm64)
container image pull --platform linux/arm64 alpine:latest
Using the Swift API Programmatically
You can invoke the same logic directly from Swift code without the CLI:
import ContainerAPIClient
let config = try await Application.loadContainerSystemConfig()
let platform = try DefaultPlatform.resolve(platform: nil, os: nil, arch: nil, log: .standard)
let image = try await ClientImage.get(
reference: "my-registry.example.com/jdoe/my-app:latest",
containerSystemConfig: config
)
try await image.push(
platform: platform,
scheme: .auto,
containerSystemConfig: config,
progressUpdate: nil
)
Summary
- Use
container image pushandcontainer image pullas the primary commands for registry operations. - Authentication is handled automatically via
Application.loadContainerSystemConfig(), which retrieves credentials from the macOS keychain. - Platform selection supports
--os,--arch,--platformflags, or theCONTAINER_DEFAULT_PLATFORMenvironment variable. - Progress reporting is built-in for both upload and download operations, with configurable display formats.
- Concurrent downloads for pull operations are controlled via the
maxConcurrentDownloadsparameter inimageFetchFlags.
Frequently Asked Questions
How does authentication work for push and pull operations?
The container CLI stores registry credentials in the macOS keychain when you run container registry login. During push and pull operations, Application.loadContainerSystemConfig() automatically retrieves these credentials from the keychain, eliminating the need to manually pass tokens in commands.
Can I pull images for a different architecture than my host?
Yes. Use the --platform flag with a value like linux/arm64 or linux/amd64, or set the CONTAINER_DEFAULT_PLATFORM environment variable. The DefaultPlatform.resolve() method handles cross-platform image selection before the download begins.
What is the difference between the --scheme flag values?
The --scheme flag accepts http, https, or auto. When set to auto, the client attempts HTTPS first and falls back to HTTP only if the registry does not support TLS. Explicitly setting http is useful for testing against insecure local registries, while https enforces encrypted connections.
How do I configure concurrent downloads for image pulls?
The pull command respects the maxConcurrentDownloads setting from imageFetchFlags, which controls how many layers download simultaneously. You can adjust this via the command-line interface to optimize bandwidth usage based on your network conditions.
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 →