How to Authenticate with Private Container Registries Using the Apple Container CLI

The Apple Container tool authenticates to private registries through a three-step CLI workflow: login, list, and logout, with all credentials securely stored in the macOS keychain via the KeychainHelper class.

The apple/container repository provides a native macOS container runtime that integrates with private OCI registries. Understanding how to authenticate with private container registries ensures secure access to your images without exposing credentials in shell history or log files.

The Three-Step Authentication Workflow

Authentication is handled through sub-commands in Sources/ContainerCommands/Registry/. The tool never writes raw passwords to disk; instead, it leverages the system keychain using the constant Constants.keychainID ("com.apple.container.registry").

Login (container registry login)

When you execute container registry login, the implementation in RegistryLogin.swift performs the following operations:

  1. Parse arguments – The command reads --username, --password-stdin, and the target registry hostname via Flags.Registry (defined in Flags.swift at line 154). The --scheme flag defaults to auto for protocol detection.

  2. Prompt for credentials – If username is omitted, KeychainHelper.userPrompt(hostname:) requests it interactively. If the password is missing, KeychainHelper.passwordPrompt() reads it from stdin (when --password-stdin is used) or interactively.

  3. Resolve connectivityReference.resolveDomain(domain:) canonicalises the hostname, while RequestScheme(registry.scheme) determines whether to use HTTP, HTTPS, or auto-detect based on Docker-compatible internal DNS logic.

  4. Validate credentials – A RegistryClient is instantiated with BasicAuthentication(username:…, password:…) and calls client.ping() to verify reachability and credential validity.

  5. Persist securely – Upon success, KeychainHelper.save(hostname:username:password:) stores the encrypted credentials in the macOS keychain.

List (container registry list)

The list sub-command (implemented in RegistryList.swift) retrieves stored credentials without exposing passwords:

  • KeychainHelper.list() returns an array of RegistryInfo objects
  • Each entry is deserialized into a RegistryResource (defined in RegistryResource.swift), containing hostname, username, and timestamps
  • Output renders as a table by default, or as a quiet list of hostnames with the -q flag

Logout (container registry logout)

To remove credentials, RegistryLogout.swift handles the logout command:

  • The supplied registry name is canonicalised via Reference.resolveDomain(domain:)
  • KeychainHelper.delete(hostname:) removes the entry from the keychain under the "com.apple.container.registry" domain

Authenticating to Non-TLS Registries

For registries without TLS certificates, force the HTTP scheme using the --scheme flag:

container registry login \
  --scheme http \
  --username alice \
  my.private.registry:5000

As implemented in Flags.swift, the --scheme option accepts http, https, or auto (default). When set to auto, RequestScheme attempts HTTPS first, falling back to HTTP only for Docker-compatible internal DNS domains.

Security Architecture and Credential Storage

The authentication flow spans three architectural layers:

CLI LayerRegistryLogin, RegistryLogout, and RegistryList extensions parse arguments and delegate to the service layer.

Service LayerRegistryClient (from the Container API client) communicates with remote registries using the resolved scheme and BasicAuthentication payload. It includes retry logic for transient failures.

Persistence LayerKeychainHelper isolates container credentials using the domain constant defined in Constants.swift. Passwords are never logged or exposed in process listings; they travel directly from stdin/interactive prompts to the encrypted keychain blob.

Practical Examples

Authenticate interactively to a standard registry:

container registry login registry.example.com

Authenticate non-interactively using stdin for the password:

container registry login \
  --username alice \
  --password-stdin \
  registry.example.com < password.txt

List all saved credentials:


# Detailed view

container registry list

# Machine-readable hostnames only

container registry list -q

Remove stored credentials:

container registry logout registry.example.com

Summary

  • Store credentials using container registry login, which persists authentication data to the macOS keychain via KeychainHelper.
  • Verify connectivity automatically through RegistryClient.ping() before saving credentials.
  • Manage entries with container registry list to view saved hosts and container registry logout to delete specific entries.
  • Force protocols with --scheme http for registries without TLS, or rely on auto-detection for Docker-compatible domains.
  • Secure by default – Credentials are encrypted under "com.apple.container.registry" and never written to logs or shell history.

Frequently Asked Questions

How does the Container tool store registry passwords?

The tool stores passwords in the macOS system keychain using the KeychainHelper class under the domain "com.apple.container.registry" (defined in Constants.swift). Passwords are read from stdin or interactive prompts and immediately encrypted, never appearing in command history or log files.

Can I use the Container CLI with HTTP-only registries?

Yes. Pass the --scheme http flag to container registry login to force HTTP instead of HTTPS. This is useful for local registries without TLS certificates. The RequestScheme logic in the API client handles the protocol selection when you use the default auto setting.

Where does the authentication logic live in the source code?

The login implementation resides in Sources/ContainerCommands/Registry/RegistryLogin.swift, logout in RegistryLogout.swift, and credential listing in RegistryList.swift. The underlying data model is defined in Sources/ContainerResource/Registry/RegistryResource.swift, while keychain constants are in Sources/Services/ContainerAPIService/Client/Constants.swift.

Why does the login command require a ping to the registry?

The client.ping() call validates that the registry is reachable and that the credentials are accepted before storing them in the keychain. This prevents saving invalid authentication data that would later cause silent failures during container run operations.

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 →