How to Configure Container Registries on Fedora

Configure Fedora container registries by editing /etc/containers/registries.conf and using podman login for authentication.

When the default registry list falls short

You type podman pull mycompany/internal-app and the terminal returns Error: unable to pull mycompany/internal-app: unable to init source: manifest unknown. You know the image exists in your private registry. You also know Fedora ships with Podman by default, but the configuration files are scattered across /etc/containers/ and ~/.config/containers/. You need a clear path to tell the container engine where to look, how to trust the connection, and where to find your credentials.

How image resolution actually works

Container engines do not guess. They follow a strict resolution order. When you request an image without a full registry URL, Podman checks a search list. It tries each registry in order until one responds with the manifest. If the registry uses self-signed certificates or runs over HTTP, Podman refuses to talk to it by default. Authentication happens separately from resolution. The engine looks for credentials in a specific order: environment variables, the user's authfile, the system authfile, and finally the legacy Docker config path. Understanding this chain stops you from editing the wrong file or fighting certificate errors.

Fedora follows the upstream containers-common convention. Configuration lives in /etc/containers/ for system-wide defaults and ~/.config/containers/ for user overrides. Files in /etc/ affect every user on the machine. Files in ~/.config/ only affect the current user. Never edit files in /usr/lib/containers/. Those ship with the package and get overwritten on updates. Always work in /etc/ or ~/.config/.

Edit the search list before you fight authentication errors. Resolution fails first.

Configure search paths and insecure registries

The primary configuration file is /etc/containers/registries.conf. It uses TOML syntax. You define three main blocks: registries.search, registries.insecure, and registries.blocked. The search block tells Podman which registries to query when you omit the registry URL. The insecure block tells Podman to skip TLS verification for specific hosts. The blocked list prevents accidental pulls from public registries like Docker Hub.

Here's how to set up a private registry search path and mark an internal mirror as insecure.

# /etc/containers/registries.conf
# Search these registries in order when a bare image name is used
[registries.search]
registries = ["registry.internal.example.com", "quay.io", "docker.io"]

# Skip TLS verification for internal registries using self-signed certs
[registries.insecure]
registries = ["registry.internal.example.com"]

# Prevent accidental pulls from public registries in production
[registries.block]
registries = ["docker.io"]

The registries.search array controls fallback behavior. Podman stops at the first match. If your internal registry does not host the image, it falls through to quay.io, then docker.io. The insecure block only disables certificate verification. It does not handle authentication. You still need credentials to pull private images. The block list is a safety net for CI runners and production servers where pulling from Docker Hub violates policy.

Restart no services. Podman reads this file on every invocation. Changes take effect immediately.

Handle authentication without hardcoding passwords

Authentication lives in a separate file. Podman stores credentials in a JSON authfile. The default location is ~/.config/containers/auth.json for user-level access and /etc/containers/auth.json for system-wide access. You can also point Podman to a custom file using the --authfile flag. This is standard practice in CI pipelines where secrets are injected as temporary files.

Run the interactive login command to populate the authfile.

# Store credentials for your internal registry in the user authfile
podman login registry.internal.example.com
# WHY: Prompts for username and password, then encrypts the token locally
# WHY: Writes the result to ~/.config/containers/auth.json by default
# WHY: Uses the system keyring if available, avoiding plaintext storage

If you are running a service account or a CI job, skip the interactive prompt and use a custom authfile.

# Create a temporary authfile for a non-interactive pipeline
podman login --authfile /tmp/ci-auth.json registry.internal.example.com
# WHY: Directs credentials to a specific path instead of the default location
# WHY: Keeps the user's home directory clean of pipeline secrets
# WHY: The file can be mounted into containers or passed to podman pull

When you pull an image, reference the authfile explicitly if it is not in the default location.

# Pull using the custom authfile and verify the search path works
podman pull --authfile /tmp/ci-auth.json internal-app:latest
# WHY: Overrides the default authfile path for this single command
# WHY: Ensures the pipeline uses the correct credentials without polluting ~/.config
# WHY: Fails fast if the token is expired or the registry rejects the scope

The authfile format is a standard JSON structure. Each registry gets a auth field containing a base64-encoded username:password string. Podman handles the encoding automatically. Never edit the authfile by hand unless you are debugging a corrupted token. Use podman logout to remove entries safely.

Clean up temporary authfiles after the build. Secrets left on disk are a deployment risk.

Verify the configuration

Run podman info to confirm the engine sees your changes. The output includes a registries section that mirrors your TOML configuration. Check the search array and the insecure list. If they match your file, the syntax is correct.

# Inspect the active registry configuration
podman info --format '{{.Store.GraphRoot}}' 2>/dev/null && podman info | grep -A 10 "registries"
# WHY: Suppresses the graph root warning that appears on fresh installs
# WHY: Filters the verbose output to show only the registry configuration block
# WHY: Confirms that TOML parsing succeeded and values are loaded into memory

Test the resolution chain with a dry run. Use podman pull with the --dry-run flag if your Podman version supports it, or simply attempt a pull of a known image and watch the terminal output. The engine prints which registry it contacts before failing or succeeding.

# Attempt a pull to verify search order and authentication
podman pull internal-app:latest
# WHY: Triggers the full resolution chain defined in registries.conf
# WHY: Validates that the authfile contains a valid token for the target host
# WHY: Prints the exact registry URL that responded with the manifest

If the pull succeeds, your configuration is active. If it fails, check the error message. The output tells you whether the failure is network, TLS, or authentication.

Run journalctl -xeu podman after a failed pull. Read the actual error before guessing.

Common pitfalls and what the error looks like

The most common failure is a mismatch between the registry URL in registries.conf and the host in the authfile. Podman matches credentials by exact hostname. If your search list uses registry.internal.example.com but your authfile contains reg.internal.example.com, the engine treats them as different hosts and refuses to send credentials. You will see Error: unauthorized: authentication required. Fix the hostname mismatch in both files.

Another frequent issue is SELinux blocking access to custom authfiles. If you mount a secret into a container or place an authfile in a non-standard directory, SELinux denies the read operation. The terminal prints Error: reading authentication file: open /tmp/ci-auth.json: permission denied. The denial is not a filesystem permission issue. It is a label mismatch. Run ls -Z /tmp/ci-auth.json to check the context. Apply the correct label with chcon -t container_share_t /tmp/ci-auth.json or place the file in a directory that already carries the container label.

Certificate verification failures appear as x509: certificate signed by unknown authority. This happens when you add a registry to registries.insecure but still use HTTPS, or when you forget to add the registry to the insecure list entirely. Podman does not fall back to HTTP automatically. It requires explicit opt-in. Add the exact hostname to the insecure array. Do not use IP addresses unless your registry literally binds to an IP. Hostname matching is strict.

Configuration drift occurs when you edit /etc/containers/registries.conf.d/*.conf but forget that the main file takes precedence if both define the same key. The containers-common package merges files in /etc/containers/registries.conf.d/ alphabetically. Conflicting keys in the main file override the drop-ins. Keep the main file clean. Put custom overrides in the conf.d directory.

Check the exact hostname in your authfile before restarting the pipeline. Mismatches cause silent authentication failures.

When to use system-wide config versus user-level auth

Use /etc/containers/registries.conf when you are managing a shared workstation or a production server where every user must pull from the same internal registry. Use ~/.config/containers/registries.conf when you are a developer who needs to test against a local registry without affecting other users on the machine. Use --authfile when you are running CI jobs or containerized builds where secrets are injected as temporary files. Use the default ~/.config/containers/auth.json when you are working interactively and want credentials to persist across sessions. Use podman logout when you rotate tokens or switch projects. Use journalctl -xe when a pull fails and you need to see whether the error is network, TLS, or authentication.

Trust the package manager. Manual file edits drift, snapshots stay.

Where to go next