A Map of podman's Configuration Files
In one line
If docker's configuration lives in a single daemon.json, podman uses several files split by role, and per-user configuration overrides system configuration.
Why this was needed
This is the first place where people coming from docker get lost. "Where do I put the registry mirror?" "Where do I change the storage path?" There is no daemon, so there is no daemon.json either.
How it works
docker podman
────────────────────────── ───────────────────────────────────────
/etc/docker/daemon.json /etc/containers/containers.conf (엔진·런타임 옵션)
~/.config/containers/containers.conf
(레지스트리 미러도 daemon.json) /etc/containers/registries.conf (레지스트리·미러·차단)
~/.config/containers/registries.conf
/etc/containers/storage.conf (스토리지 드라이버·경로)
~/.config/containers/storage.conf
/etc/containers/policy.json (이미지 서명 정책)
~/.docker/config.json (인증) ~/.config/containers/auth.json (레지스트리 인증)
이미지·컨테이너 저장 위치
/var/lib/docker rootful: /var/lib/containers/storage
rootless: ~/.local/share/containers/storage
The per-user file overrides the system file. That is why most rootless configuration happens under ~/.config/containers/.
storage.conf
[storage]
driver = "overlay"
runroot = "/run/user/1000/containers"
graphroot = "/home/podster/.local/share/containers/storage"
[storage.options]
additionalimagestores = []
[storage.options.overlay]
mount_program = "/usr/bin/fuse-overlayfs"
graphroot— where image and container layers pile up. It is the main consumer of disk.runroot— runtime state (locks, temporary mounts). It must be tmpfs. This prevents the incident where lock files left over after a reboot block containers from starting.driver— in rootless mode,overlay(+ fuse-overlayfs) orvfs. vfs copies everything for every layer, so it is slow and uses a lot of space, but it works anywhere.
registries.conf
unqualified-search-registries = ["docker.io"]
[[registry]]
prefix = "docker.io"
location = "mirror.internal.example.com/dockerhub"
[[registry]]
location = "blocked.example.com"
blocked = true
unqualified-search-registries is the typical difference from docker. docker automatically resolves nginx to docker.io, but podman follows the configuration or asks which registry you mean. In a CI script, the most robust approach is to use the full path, such as docker.io/library/nginx.
containers.conf
It sets engine defaults: the default OCI runtime (crun/runc), the default network backend (netavark/cni), the default cgroup manager, default environment variables, and so on.
[engine]
runtime = "crun"
cgroup_manager = "cgroupfs"
[containers]
default_ulimits = ["nofile=65535:65535"]
If rootless has no systemd or cgroup delegation is unavailable, you must set cgroup_manager = "cgroupfs".
What it looks like in the field
The initial setup checklist. When you set up rootless podman on a new server, there is a fixed order of things to check.
- Is there a dedicated user with a home directory?
- Do
/etc/subuidand/etc/subgidcontain a range for that user? - Are
newuidmap/newgidmapinstalled? - Is
graphrooton a partition with enough space? - Is
runrooton tmpfs? - Does
registries.confhave the internal mirror and the unqualified search setting? - In
podman info, are rootless=true, the driver, and the paths as expected?
Item 7 is the final verification. Many teams parse podman info --format json and check it automatically in CI.
What you will do in the next lab
You add subuid/subgid for the user podster, calculate the mapping by hand, write storage.conf and registries.conf, and verify everything with podman info.