# How to Push and Pull Container Images from Registries with Apple's Container Tool

> Learn to push and pull container images using Apple's container tool. Effortlessly transfer images to OCI registries with automatic credential handling from the macOS keychain.

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

---

**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`](https://github.com/apple/container/blob/main/ImagePush.swift), while **`ImagePull`** manages downloads in [`ImagePull.swift`](https://github.com/apple/container/blob/main/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`](https://github.com/apple/container/blob/main/Sources/ContainerCommands/Image/ImagePull.swift#L71), this configuration determines how the client authenticates to private registries.

```swift
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`](https://github.com/apple/container/blob/main/Sources/ContainerCommands/Image/ImagePull.swift#L73) and [`ImagePush.swift#L61`](https://github.com/apple/container/blob/main/Sources/ContainerCommands/Image/ImagePush.swift#L61).

```swift
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`](https://github.com/apple/container/blob/main/Sources/ContainerCommands/Image/ImagePush.swift#L63) for push and [`ImagePull.swift#L75`](https://github.com/apple/container/blob/main/Sources/ContainerCommands/Image/ImagePull.swift#L75) for pull.

```swift
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`](https://github.com/apple/container/blob/main/Sources/ContainerCommands/Image/ImagePull.swift#L77). For **push** operations, it retrieves the local image via `ClientImage.get()` at [`ImagePush.swift#L64`](https://github.com/apple/container/blob/main/Sources/ContainerCommands/Image/ImagePush.swift#L64).

```swift
// 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`](https://github.com/apple/container/blob/main/Sources/ContainerCommands/Image/ImagePull.swift#L86-L90) and [`ImagePush.swift#L74-L78`](https://github.com/apple/container/blob/main/Sources/ContainerCommands/Image/ImagePush.swift#L74-L78).

```swift
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`](https://github.com/apple/container/blob/main/Sources/ContainerCommands/Image/ImagePull.swift#L96-L100) supports the `maxConcurrentDownloads` parameter from `imageFetchFlags`.

```swift
// 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:

```bash

# 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:

```bash

# 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:

```swift
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 push`** and **`container image pull`** as 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`, `--platform` flags, or the `CONTAINER_DEFAULT_PLATFORM` environment 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 `maxConcurrentDownloads` parameter in `imageFetchFlags`.

## 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.