Podman `containers.conf` Explained: Defaults, Overrides and Modules

Tested on Red Hat Enterprise Linux 10.2 (Coughlan)
Package podman-5.8.2-5.el10_2.x86_64
Applies to Any Linux host with Podman installed
Privilege Rootful and rootless examples; system drop-ins need administrator access
Scope How Podman loads containers.conf — search paths, drop-in precedence, [containers], [network], [engine], and [secret] highlights, CONTAINERS_CONF and CONTAINERS_CONF_OVERRIDE, opt-in --module profiles, string-array {append=true}, and verification with podman info and container inspection. Does not cover storage.conf, registries.conf depth, full secrets workflows, or exhaustive every-key reference.

Repeating the same flags on every podman run gets old fast. containers.conf is where Podman reads persistent defaults — environment variables, capability sets, logging, network tooling, event destinations — before your command-line options apply. This guide maps the file locations, shows which layer wins when several files disagree, and covers modules and append syntax that rarely appear in thin tutorials.

If you only need one persistent tweak, a drop-in under containers.conf.d/ is usually enough. Reach for CONTAINERS_CONF, modules, or append markers when you are debugging precedence, running CI with isolated settings, or building reusable profiles that should not load on every host by default.


What is containers.conf?

containers.conf supplies default configuration consumed by Podman and other tools in the containers/common stack. Think of the flow as:

text
containers.conf defaults
Podman command
        + command-line flags
effective runtime configuration

Command-line flags override many configured defaults for a single invocation. The file shapes what happens when you omit those flags — default environment, capabilities, log driver, rootless network command, events logger, runtime selection, and secret driver backend.

Registry hostname routing lives in registries.conf; disk layout lives in storage.conf. This page stays on containers.conf behavior only.


Find the active containers.conf files

Configuration is layered. Typical paths, from vendor baseline through administrator and user overrides:

Layer Path
Vendor /usr/share/containers/containers.conf
Administrator main /etc/containers/containers.conf (optional — many hosts use drop-ins only)
Administrator drop-ins /etc/containers/containers.conf.d/*.conf
Rootless-specific /etc/containers/containers.rootless.conf, containers.rootless.d/*.conf, containers.rootless.d/$UID/*.conf
User main $XDG_CONFIG_HOME/containers/containers.conf or ~/.config/containers/containers.conf
User drop-ins ~/.config/containers/containers.conf.d/*.conf

On this lab host the vendor file exists but /etc/containers/containers.conf does not — only drop-ins under containers.conf.d/ customize the system layer.

Rootful and rootless sessions do not always read identical paths. Rootless Podman may also load /etc/containers/containers.rootless.conf and files under containers.rootless.d/, including per-UID directories when your distribution ships them. If a system-wide drop-in changes rootful behavior but a rootless user sees something else, compare paths as that user before assuming the administrator file was ignored.

List what is present before you edit:

bash
ls -la /usr/share/containers/containers.conf /etc/containers/containers.conf /etc/containers/containers.conf.d/ 2>/dev/null

The vendor file is long but mostly commented examples. Read it once to see which keys your distribution ships disabled by default — pasta networking, compose provider order, and capability lists are often documented there with # prefixes you can copy into a drop-in instead of inventing syntax from memory.

Later-loaded values override earlier ones for the same key within the normal merge rules. The exact merge order is documented in containers.conf(5); the sections below prove behavior with podman info rather than only listing directories.


Prefer drop-ins for small overrides

Copying the entire vendor file into /etc/containers/containers.conf makes upgrades painful. Add a small drop-in instead:

text
/etc/containers/containers.conf.d/90-custom.conf

Example:

toml
[engine]
events_logger = "journald"

Drop-ins keep local changes visible, preserve vendor defaults you did not touch, and make rollback a single rm of your file. Per-user tweaks belong under ~/.config/containers/containers.conf.d/ when you need settings that apply only to one account.

Numeric prefixes such as 90-custom.conf are a convention, not a Podman requirement. Files in containers.conf.d/ load in lexical order, so 10-foo.conf merges before 90-bar.conf. When two drop-ins set the same key, the later filename wins unless a more specific layer (user config, environment variable, or command flag) overrides it afterward.


See configuration precedence in practice

Layering is easier to trust after you watch one field change. Add a system drop-in:

toml
# /etc/containers/containers.conf.d/90-events.conf
[engine]
events_logger = "journald"

Add a user drop-in that sets the same key differently:

toml
# ~/.config/containers/containers.conf.d/10-user-events.conf
[engine]
events_logger = "file"

Read the effective value Podman reports:

bash
podman info --format '{{.Host.EventLogger}}'

Sample output when the same user's ~/.config/containers/... drop-in is present:

output
file

User configuration is loaded after administrator configuration, so the user value wins. Verify with podman info rather than assuming file order alone.


[containers] defaults that matter

The [containers] table sets defaults for every container unless a flag overrides them. You do not need every key from the man page — these are the ones administrators touch most often:

Key Controls
env Default environment variables injected into containers
default_capabilities Capability set added when --cap-add / --cap-drop are not used
pids_limit Process limit inside the container
log_driver Logging backend (k8s-file, journald, etc.)
tz Container timezone
umask Umask for container processes
label SELinux label behavior
netns Default network namespace mode
userns Default user namespace mode
volumes Default volume mounts
devices Default device nodes
init Whether an init process is inserted
dns_servers, dns_searches, dns_options Default resolver settings written into container /etc/resolv.conf

This article demonstrates env and points at the others so you know which table to open — not as a hardening checklist.

A pids_limit drop-in shows the same pattern as environment defaults. Add to a drop-in:

toml
[containers]
pids_limit = 512

The CLI equivalent for one container is podman run --pids-limit 512. Podman does not print the limit in podman info; start a short-lived container and read the stored limit:

bash
podman run -d --name pids-demo registry.access.redhat.com/ubi9/ubi-minimal:latest sleep 120

HostConfig.PidsLimit in inspect output is the value Podman applied at create time:

bash
podman inspect pids-demo --format '{{.HostConfig.PidsLimit}}'

Sample output when the drop-in is active:

output
512

If you omit pids_limit from containers.conf, Podman falls back to vendor defaults or leaves the limit unset depending on version and cgroup configuration.


Set default environment variables

Add a drop-in with a default environment entry:

toml
# /etc/containers/containers.conf.d/91-env.conf
[containers]
env = ["APP_ENV=production"]

Run a container that prints the variable (the lab uses a minimal image with printenv):

bash
podman run --rm registry.access.redhat.com/ubi9/ubi-minimal:latest printenv APP_ENV

Sample output:

output
production

A command-line -e overrides the default for that run:

bash
podman run --rm -e APP_ENV=staging registry.access.redhat.com/ubi9/ubi-minimal:latest printenv APP_ENV

Sample output:

output
staging

Defaults come from containers.conf; explicit flags win for the invocation you are running.


[network] defaults

The [network] table controls networking defaults such as the network backend, subnet pools, network configuration paths, and rootless networking tooling. RHEL 10 ships with pasta commented in the vendor file:

text
#default_rootless_network_cmd = "pasta"

Confirm the effective network settings Podman is using:

bash
podman info --format 'rootlessNetworkCmd={{.Host.RootlessNetworkCmd}} networkBackend={{.Host.NetworkBackend}}'

Sample output:

output
rootlessNetworkCmd=pasta networkBackend=netavark

Other keys you may see in production configs include network_config_dir, default_subnet_pools, and pasta_options. DNS defaults such as dns_servers, dns_searches, and dns_options belong under [containers], not [network].

To pin rootless networking explicitly, uncomment or add in a drop-in:

toml
[network]
default_rootless_network_cmd = "pasta"

After a change under [network], restart long-running rootless pods or verify with a fresh podman run — existing network namespaces keep the settings they were created with. Full networking walkthroughs belong in Podman networking — here the point is which table owns those defaults.


[engine] defaults

The [engine] table contains defaults for Podman engine behavior:

Key Purpose
events_logger Where lifecycle events are recorded (file, journald, …)
runtime OCI runtime path (typically crun or runc)
helper_binaries_dir Directory for network and utility helpers
service_timeout Remote service timeout
compose_providers Ordered list for podman compose provider selection
hooks_dir OCI hook directories
podmansh_timeout Timeout for podmansh login shells

Example drop-in:

toml
[engine]
events_logger = "journald"

Verify:

bash
podman info --format '{{.Host.EventLogger}}'

The precedence section above used this same field. compose_providers is covered next because it is a common practical tweak.


Compose provider configuration

podman compose is a wrapper that delegates to an external Compose implementation. The vendor file documents:

toml
#compose_providers=[]

When unset, Podman picks an available provider (often preferring docker-compose over podman-compose when both exist — behavior can vary by version). Set explicit order in a drop-in:

toml
[engine]
compose_providers = ["podman-compose", "docker-compose"]

Decision tables, socket workflows, and provider differences live in Podman Compose. containers.conf only names which provider to try first.


[secret] and [secret.opts]

Default secret backend selection also lives here:

toml
[secret]
driver = "file"

That changes which backend Podman uses when you do not pass --secret driver flags. Creating and mounting secrets is covered in Manage Podman secrets.


Replace normal config with CONTAINERS_CONF

CONTAINERS_CONF is stronger than adding another drop-in. When set, Podman uses only that file as the normal configuration source — system and user containers.conf trees are ignored.

Create an isolated test file:

toml
# /tmp/test-containers.conf
[engine]
events_logger = "file"

[containers]
env = ["LAB_MARKER=containers_conf_only"]

Point Podman at it:

bash
export CONTAINERS_CONF=/tmp/test-containers.conf

Check the events logger:

bash
podman info --format '{{.Host.EventLogger}}'

Sample output:

output
file

Confirm a variable from the test file is present while system drop-ins are ignored:

bash
podman run --rm registry.access.redhat.com/ubi9/ubi-minimal:latest printenv LAB_MARKER

Sample output:

output
containers_conf_only

Unset the variable when you finish testing — otherwise every Podman command keeps using the isolated file:

bash
unset CONTAINERS_CONF CONTAINERS_CONF_OVERRIDE

Forgetting to unset CONTAINERS_CONF can make administrator and user drop-ins appear to be ignored.


Add a final override with CONTAINERS_CONF_OVERRIDE

CONTAINERS_CONF_OVERRIDE loads one more file after everything else in the normal merge, including when CONTAINERS_CONF is set.

With CONTAINERS_CONF=/tmp/test-containers.conf still exported, create:

toml
# /tmp/final.conf
[engine]
events_logger = "journald"

Apply the override:

bash
export CONTAINERS_CONF_OVERRIDE=/tmp/final.conf
podman info --format '{{.Host.EventLogger}}'

Sample output:

output
journald

The override file changed events_logger from file to journald even though CONTAINERS_CONF alone specified file. Use this in CI or debugging when you need one forced field without rebuilding a full config tree.


Compare CONTAINERS_CONF and CONTAINERS_CONF_OVERRIDE

Variable Behavior
CONTAINERS_CONF Replaces the normal system/user config search path with a single file
CONTAINERS_CONF_OVERRIDE Loads last as a final override on top of the already-merged configuration

Setting only CONTAINERS_CONF explains “my /etc/containers edits do nothing.” Setting CONTAINERS_CONF_OVERRIDE explains “one field changed in tests but nothing else did.”

Neither variable replaces storage.conf or registries.conf. Those files follow their own precedence rules and environment variables (CONTAINERS_STORAGE_CONF, registry config paths). Mixing concerns across files is another frequent misconfiguration: a pull mirror belongs in registries.conf, while default capabilities belong in containers.conf.


Podman configuration modules

Modules are reusable configuration profiles stored outside the automatic drop-in merge. Search paths include:

text
$XDG_CONFIG_HOME/containers/containers.conf.modules/
~/.config/containers/containers.conf.modules/
/etc/containers/containers.conf.modules/
/usr/share/containers/containers.conf.modules/

Modules are not loaded automatically. Opt in per command with --module as a global flag before the subcommand:

bash
podman --module dev.conf run --rm IMAGE printenv ENVIRONMENT

Placing --module after run fails:

output
Error: unknown flag: --module

Create a module file:

toml
# /etc/containers/containers.conf.modules/dev.conf
[containers]
env = ["ENVIRONMENT=development"]

Load it (rootful sessions resolve module names from /etc/containers/containers.conf.modules/):

bash
podman --module dev.conf run --rm registry.access.redhat.com/ubi9/ubi-minimal:latest printenv ENVIRONMENT

Sample output:

output
development

Use the filename including .confpodman --module dev without the suffix looks for a directory named dev and fails on this host.


Load multiple modules

Pass --module more than once. Later modules override earlier ones for the same key:

bash
podman --module module1.conf --module module2.conf run --rm registry.access.redhat.com/ubi9/ubi-minimal:latest printenv MODULE_VAR

When module2.conf sets MODULE_VAR=from_module2, sample output:

output
from_module2

Modules load after normal system/user configuration and before CONTAINERS_CONF_OVERRIDE. They are opt-in profiles, not substitutes for drop-ins you want on every command.

Typical uses include a dev.conf module with relaxed labels for local hacking, a ci.conf module checked into a pipeline repository and loaded by absolute path, or a performance.conf module with tuned pids_limit and logging that operators enable only during benchmarks. None of those profiles apply until someone passes --module, which keeps production defaults safe on shared hosts.


Load a module by absolute path

For CI or one-off tests, point at a file directly and run a container that reads a variable from the module:

bash
podman --module /tmp/testing.conf run --rm registry.access.redhat.com/ubi9/ubi-minimal:latest printenv ABS_MODULE

With /tmp/testing.conf containing env = ["ABS_MODULE=absolute_path_works"], sample output:

output
absolute_path_works

Absolute paths skip module search directories entirely.


Append to string arrays instead of replacing them

Later TOML normally replaces entire string arrays. Two drop-ins:

toml
# 92-append-base.conf
[containers]
env = ["ONE=1"]
toml
# 93-append-second.conf
[containers]
env = ["TWO=2"]

Only the last file's values survive:

bash
podman run --rm registry.access.redhat.com/ubi9/ubi-minimal:latest printenv ONE TWO

Sample output:

output
2

ONE is unset because the second file replaced the whole env array.

Enable append on the later file:

toml
[containers]
env = ["TWO=2", {append=true}]

Retry:

bash
podman run --rm registry.access.redhat.com/ubi9/ubi-minimal:latest printenv ONE TWO

Sample output:

output
1
2

Once {append=true} is set for that array, subsequent loading steps keep appending until a later file sets {append=false} to return to replace behavior. The same pattern applies to other string arrays such as default_capabilities and volumes.


Check effective configuration

Podman does not ship one command that dumps every merged key. Use observable output instead:

bash
podman info

Fields such as eventLogger, rootlessNetworkCmd, and networkBackend reflect [engine] and [network] choices. For container defaults, start a disposable container and inspect its environment list:

bash
podman run -d --name conf-check registry.access.redhat.com/ubi9/ubi-minimal:latest sleep 300

podman inspect shows the merged env array Podman applied at create time:

bash
podman inspect conf-check --format '{{.Config.Env}}'

Sample output includes defaults from active drop-ins (your list will differ):

output
[PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin APP_ENV=production ...]

Match what you see against your drop-ins, modules, and environment variables. When results disagree with expectations, re-check user drop-ins, CONTAINERS_CONF, and module opt-in flags before editing the vendor file.


Troubleshooting

Symptom Likely cause Fix
Edited /etc/containers/containers.conf but rootless behavior unchanged User drop-ins or ~/.config/containers/ override system files Inspect user paths; test with podman info as that user
System drop-ins seem ignored CONTAINERS_CONF points at an isolated file Unset CONTAINERS_CONF or include needed keys in that file
Module file exists but nothing changes Modules are not auto-loaded Use podman --module name.conf before the subcommand
--module unknown flag on podman run Flag placed after subcommand Use podman --module … run …
Vendor env values disappeared after a drop-in Array replaced, not appended Add {append=true} on the later env entry

References


Summary

containers.conf is how Podman remembers defaults between commands. Vendor files under /usr/share/containers/ start the stack; administrator and user drop-ins override specific keys; command-line flags override defaults for one invocation. podman info is the quickest sanity check when you need to know which layer won.

CONTAINERS_CONF replaces the entire normal search path with one file — powerful for tests, surprising when you forget it is set. CONTAINERS_CONF_OVERRIDE applies one last file on top. Modules sit between those layers: opt-in profiles via podman --module, global flag first, filename or absolute path.

String-array append syntax fixes the other common foot-gun: without {append=true}, a later drop-in silently replaces the whole array. When you add a user drop-in and vendor environment entries vanish, append is usually the fix — not another copy of the vendor file.

Registry routing and image storage live in sibling files — use Configure registries.conf and Podman storage location when the task is pulls or disk layout, not container defaults. For day-to-day operations, keep system policy in numbered drop-ins, personal tweaks under ~/.config/containers/, and reserve modules for profiles you want to opt into explicitly.


Frequently Asked Questions

1. Where does Podman read containers.conf on Linux?

Podman layers vendor files under /usr/share/containers/, administrator files under /etc/containers/containers.conf and containers.conf.d, optional rootless-specific paths, and per-user files under ~/.config/containers/. Later-loaded files override earlier values for the same key. Setting CONTAINERS_CONF replaces that entire normal search path with one file.

2. What is the difference between CONTAINERS_CONF and CONTAINERS_CONF_OVERRIDE?

CONTAINERS_CONF points at a single file that becomes the only normal configuration source — system and user containers.conf files are ignored. CONTAINERS_CONF_OVERRIDE loads one additional file last, on top of whatever configuration was already merged, including when CONTAINERS_CONF is set.

3. Why does my containers.conf module have no effect?

Modules are not loaded automatically. Pass podman --module NAME before the subcommand, for example podman --module dev.conf run. The module file must live under a containers.conf.modules search directory or be given as an absolute path.

4. Why did my env array drop vendor defaults?

Later TOML files replace entire string arrays by default. To keep earlier values and add more, use the append marker in the array, for example env = ["TWO=2", {append=true}].

5. Should I edit containers.conf to change where images are stored?

No. Image and container storage paths belong in storage.conf, not containers.conf. See the Podman storage location guide for graphroot, runroot, and storage.conf precedence.
Deepak Prasad

R&D Engineer

Founder of GoLinuxCloud with more than 15 years of expertise in Linux, Python, Go, Laravel, DevOps, Kubernetes, Git, Shell scripting, OpenShift, AWS, Networking, and Security. With extensive experience, he excels across development, DevOps, networking, and security, delivering robust and efficient solutions for diverse projects.

  • Go (programming language)
  • Python (programming language)
  • DevOps
  • Computer Security
  • Cloud Computing
  • Kubernetes
  • Linux
  • Ansible (software)