How to Handle Registry Authentication and Credentials in the Apple Container CLI
The Apple Container CLI stores registry credentials securely in macOS Keychain via the KeychainHelper class, enabling automatic authentication for push and pull operations after an initial container registry login.
The apple/container project provides a native macOS container management solution that handles registry authentication and credentials through system Keychain integration rather than insecure file-based storage. This approach ensures that sensitive authentication data remains encrypted and accessible only to the user, while supporting both interactive and automated workflows.
Authentication Flow
The authentication system follows a strict flow: credentials are verified against the registry, stored in Keychain, and then retrieved automatically for subsequent operations.
Logging In (container registry login)
The login implementation resides in Sources/ContainerCommands/Registry/RegistryLogin.swift. The command accepts --username and --password-stdin flags, or prompts interactively when omitted.
The process executes as follows:
- Host Resolution: The code calls
Reference.resolveDomainto determine the target registry host and automatically detects the scheme (http,https, orauto) based on system DNS configuration. - Credential Verification: A
RegistryClientis instantiated with aBasicAuthenticationpayload containing the provided credentials. The client performs a ping request to verify validity before storage. - Secure Storage: Upon successful verification, the system calls
KeychainHelper.save(hostname:username:password:)to persist credentials under the security domain defined byConstants.keychainID.
Logging Out (container registry logout)
To remove credentials, the logout command—implemented in Sources/ContainerCommands/Registry/RegistryLogout.swift—resolves the registry host and invokes KeychainHelper.delete(hostname:). This operation requires no network request; it simply removes the entry from the macOS Keychain.
Automatic Credential Retrieval
When executing registry-related operations like container image push or pull, the system instantiates KeychainHelper (observed in Sources/Services/MachineAPIService/Client/MachineClient.swift at line 336 and Sources/Services/ContainerImagesService/Server/ImagesService.swift at line 426) with Constants.keychainID.
The helper calls userPrompt(hostname:) or passwordPrompt() only when no stored credentials exist. If valid credentials are found, they are returned transparently, bypassing manual input.
Configuration and Defaults
The default registry domain is defined in the system configuration under the [registry] table:
[registry]
domain = "docker.io"
Users can override this default by creating or editing ~/.config/container/config.toml. This configuration affects how image references are resolved when no explicit host is specified (e.g., myapp:latest resolves to my-registry.example.com/library/myapp:latest).
Security Guarantees
The authentication architecture provides three critical security layers:
- Keychain Isolation: Credentials are stored under a unique security domain (
Constants.keychainID), preventing cross-application access or exposure to other processes. - Memory Safety: Passwords remain in memory only until verified and saved; the code never writes secrets to logs, environment variables, or temporary files.
- Phishing Mitigation: When executing
container registry loginwithout the--usernameflag, the system prompts the user to confirm the hostname via the Keychain dialog, preventing credential theft through spoofed registry endpoints.
Practical Examples
Interactive Login
# Prompts for username and password via secure dialog
container registry login my-registry.example.com
Non-Interactive Login (CI/CD)
echo "$MY_PASSWORD" | container registry login \
--username myuser --password-stdin my-registry.example.com
Automatic Push Using Stored Credentials
# Uses credentials previously saved in Keychain
container image push my-registry.example.com/myuser/myapp:latest
Logout and Credential Removal
container registry logout my-registry.example.com
Override Default Registry
Create ~/.config/container/config.toml:
[registry]
domain = "my-registry.example.com"
Key Implementation Files
- Sources/ContainerCommands/Registry/RegistryLogin.swift: Parses login options, implements
BasicAuthenticationverification, and persists credentials viaKeychainHelper. - Sources/ContainerCommands/Registry/RegistryLogout.swift: Handles credential deletion from Keychain without network operations.
- Sources/Services/MachineAPIService/Client/MachineClient.swift (line 336): Demonstrates
KeychainHelperinstantiation for machine-related registry operations. - Sources/Services/ContainerImagesService/Server/ImagesService.swift (line 426): Shows credential retrieval for image push/pull operations.
- docs/container-system-config.md: Documents the
[registry]configuration table and default domain settings.
Summary
- Keychain Storage: All credentials are stored in macOS Keychain under
Constants.keychainID, not in plaintext files. - Login Flow: The
RegistryLogin.swiftimplementation verifies credentials via ping before saving them usingKeychainHelper.save(). - Automatic Retrieval:
ImagesService.swiftandMachineClient.swiftautomatically retrieve stored credentials, bypassing prompts when available. - Configuration: Default registries are configured via
~/.config/container/config.tomlunder the[registry]table. - Security: The system prevents credential logging and requires explicit user consent for unknown hosts.
Frequently Asked Questions
Where exactly are my registry credentials stored?
Credentials are stored in the macOS Keychain under a unique security domain defined by Constants.keychainID. According to the source code in RegistryLogin.swift, the KeychainHelper.save(hostname:username:password:) method writes them to the user's default keychain, making them accessible only to the Container CLI process and requiring macOS user authentication for access.
How do I handle registry authentication in CI/CD pipelines without interactive prompts?
Use the --password-stdin flag combined with --username for non-interactive authentication. As implemented in RegistryLogin.swift, the command accepts piped input: echo "$PASSWORD" | container registry login --username $USER --password-stdin $HOST. This bypasses the interactive userPrompt() and passwordPrompt() calls while still securely storing credentials in Keychain for subsequent push operations.
Can I use multiple private registries simultaneously?
Yes. The KeychainHelper stores credentials keyed by hostname (via Reference.resolveDomain), allowing distinct entries for docker.io, ghcr.io, and private registries. When executing operations like container image push, the system extracts the domain from the image reference and retrieves the matching credentials automatically, as seen in the ImagesService.swift implementation.
What happens if I deny Keychain access when prompted?
If you deny Keychain access, the KeychainHelper cannot retrieve or save credentials, causing the authentication flow to fail. The system will either prompt again (for interactive sessions) or fail with an authentication error (for non-interactive sessions). You must grant Keychain access for the Constants.keychainID security domain to enable credential persistence.
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 →