How Container Authenticates with Registries Using macOS Keychain

Container stores registry credentials in the macOS Keychain via the KeychainHelper wrapper, isolating secrets from plain-text files and enabling secure authentication through login, logout, and automatic retrieval during image pulls.

The apple/container tool leverages macOS native security infrastructure to handle OCI registry authentication. By using the Keychain as the credential store, Container ensures that registry passwords never reside in unencrypted configuration files, instead relying on the system's secure enclave for storage and retrieval.

The Keychain-Based Authentication Architecture

Container isolates registry credentials from its configuration by storing them as generic password items in the macOS Keychain. This architecture centers on the KeychainHelper class provided by the internal ContainerPersistence package, which acts as a thin wrapper around Apple's Security framework.

All Keychain entries are namespaced using a unique security domain defined by Constants.keychainID in Sources/ContainerCommands/Registry/Constants.swift. This prevents credential collisions with other applications and ensures that Container only accesses its own registry secrets.

Registry Login Workflow

The authentication flow begins with the container registry login command, implemented in Sources/ContainerCommands/Registry/RegistryLogin.swift.

Credential Prompting and Validation

The command parses optional --username and --password-stdin flags. If a username is not provided, KeychainHelper.userPrompt(hostname:) retrieves any previously saved username for that host or prompts the user interactively. For passwords, KeychainHelper.passwordPrompt() reads input securely from the terminal with echo disabled.

Before storage, Container validates the credentials by creating a RegistryClient with BasicAuthentication(username:password:) and invoking ping() to verify connectivity and authentication against the registry.

Secure Storage with KeychainHelper

Upon successful validation, the credentials persist via keychain.save(hostname:username:password:), which stores them as a generic password item keyed by the hostname.

// Simplified workflow from RegistryLogin.swift
let keychain = KeychainHelper(securityDomain: Constants.keychainID)

if username.isEmpty {
    username = try keychain.userPrompt(hostname: server)
}
if password.isEmpty {
    password = try keychain.passwordPrompt()
    print() // newline after hidden password entry
}

// Validate before storing
let client = RegistryClient(
    host: host,
    scheme: scheme.rawValue,
    port: url.port,
    authentication: BasicAuthentication(username: username, password: password),
    retryOptions: .default
)

try await client.ping()
try keychain.save(hostname: server, username: username, password: password)

Registry Logout and Credential Management

Container provides explicit commands to manage stored credentials without manual Keychain manipulation.

Removing Stored Credentials

The container registry logout command resolves the registry hostname and calls keychain.delete(hostname:), removing the entry from the Keychain entirely. This implementation resides in Sources/ContainerCommands/Registry/RegistryLogout.swift.

// From RegistryLogout.swift
let keychain = KeychainHelper(securityDomain: Constants.keychainID)
let host = Reference.resolveDomain(domain: registry)
try keychain.delete(hostname: host)

Listing Saved Registries

The container registry list command invokes KeychainHelper.list() to display all stored registry entries for the configured security domain. This allows users to audit which registries have active credentials without exposing the underlying passwords. The implementation is located in Sources/ContainerCommands/Registry/RegistryList.swift.

How KeychainHelper Works

KeychainHelper abstracts macOS Security framework interactions through a concise API:

  • init(securityDomain:) – Initializes the helper with Constants.keychainID to namespace all entries.
  • userPrompt(hostname:) – Retrieves stored usernames or prompts interactively.
  • passwordPrompt() – Reads passwords securely from /dev/tty with no echo.
  • save(hostname:username:password:) – Creates or updates generic password items.
  • delete(hostname:) – Removes specific entries by hostname.
  • list() – Returns all stored credentials for the security domain.

Because credentials live in the system Keychain, they benefit from macOS security policies including automatic screen-lock protection, Touch ID integration, and hardware-backed encryption.

End-to-End Authentication Flow

  1. Initial Login – When running container registry login my.registry.com, the tool creates a KeychainHelper instance, prompts for missing credentials, validates them via RegistryClient.ping(), and stores them via keychain.save().

  2. Subsequent Operations – Commands like container run or container pull automatically retrieve credentials from the Keychain using keychain.load(hostname:) (internal to the image-pull logic) without requiring user interaction.

  3. Explicit Logout – Running container registry logout ensures stale credentials are removed via keychain.delete(), preventing unauthorized access to private registries.

Summary

  • Container uses the macOS Keychain via KeychainHelper to store credentials securely outside of configuration files.
  • The login command validates credentials using RegistryClient.ping() before persisting them with keychain.save(hostname:username:password:).
  • Credentials are namespaced using Constants.keychainID to prevent collisions with other applications.
  • The logout command removes entries using keychain.delete(hostname:), while list displays stored registries without exposing passwords.
  • Image pull operations automatically retrieve credentials from the Keychain, enabling seamless authentication workflows.

Frequently Asked Questions

How does Container store registry credentials securely?

Container stores credentials as generic password items in the macOS Keychain using the KeychainHelper class from the ContainerPersistence package. This ensures secrets are encrypted and protected by the user's login password or Touch ID, rather than being stored in plain-text configuration files like ~/.docker/config.json.

What happens when I run container registry login?

The RegistryLogin command creates a KeychainHelper instance with Constants.keychainID and prompts for credentials if not provided via --username or --password-stdin. It validates the credentials using RegistryClient.ping() before calling keychain.save(hostname:username:password:) to persist them in the Keychain under the Container security domain.

How can I check which registries have stored credentials?

Run container registry list, which invokes KeychainHelper.list() to retrieve all stored entries for the Container security domain. This displays all registries that currently have saved credentials without exposing the actual passwords, as implemented in Sources/ContainerCommands/Registry/RegistryList.swift.

Is the Keychain integration mandatory for Container?

Yes, the Keychain is the mandatory credential store for registry authentication in Container. The tool does not support unencrypted credential files, ensuring that all registry passwords benefit from macOS security policies including automatic locking and hardware-backed encryption through the Secure Enclave.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →