Podman Storage Location and `storage.conf` Explained

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 examples as root; rootless examples as an unprivileged user
Scope Where Podman stores images, containers, and volumes — podman info, graphroot, runroot, volumePath, storage.conf precedence, overlay vs fuse-overlayfs, changing graphroot, SELinux relabel on move, imagestore, additionalimagestores, rootless NFS limits, and transient_store. Does not cover disk-full recovery, storage corruption repair, volume tutorials, or SELinux mount permissions.
Related guides Rootless Podman
Bind mount vs volume
Install Podman on RHEL

Podman does not scatter images and containers at random paths under /var/lib. It stores everything through the containers/storage library, driven by storage.conf. Do not rely on memorized default paths — ask the Podman installation you are running right now.


Where does Podman store containers and images?

The supported answer is always podman info. It reports the active paths, driver, and configuration file for this host and user.

As root on the lab host:

bash
podman info --format 'graphRoot={{.Store.GraphRoot}} runRoot={{.Store.RunRoot}} volumePath={{.Store.VolumePath}}'

Sample output:

output
graphRoot=/var/lib/containers/storage runRoot=/run/containers/storage volumePath=/var/lib/containers/storage/volumes

Those three paths are the practical answer for rootful Podman on this machine. For the full store block in one JSON object:

bash
podman info --format '{{json .Store}}'

The graphRoot, runRoot, volumePath, graphDriverName, graphStatus, graphOptions, configFile, and transientStore fields are the ones to read first.

Typical defaults when no override exists:

Mode Typical graphroot Typical runroot Typical volumePath
Rootful /var/lib/containers/storage /run/containers/storage /var/lib/containers/storage/volumes
Rootless $HOME/.local/share/containers/storage /run/user/UID/containers under rootless graphroot/volumes

Rootless paths follow $XDG_DATA_HOME when set. graphroot and runroot come from containers/storage configuration, while Podman's volumePath normally follows the storage location but can be overridden separately with volume_path in containers.conf. Always confirm the effective paths with podman info.


Understand graphroot, runroot, and volumePath

Path or setting Purpose
graphroot Primary persistent read/write container storage
runroot Temporary runtime storage
volumePath Location of Podman-managed volumes
imagestore Optional separate location for newly stored images
additionalimagestores Additional read-only image store paths

graphroot

graphroot holds persistent containers/storage data:

  • image and container layer storage managed by the graph driver
  • container writable layer content
  • storage metadata databases

These are implementation directories. Manage lifecycle through Podman commands — do not edit overlay databases by hand.

runroot

runroot is temporary writable state for the storage driver at runtime. Rootful Podman on the lab host uses /run/containers/storage; rootless uses a path under /run/user/UID/containers. Data here is not your application's long-term store — it is runtime scratch space that disappears on reboot.

Volume path

Named volumes created by Podman's default volume driver normally live below the storage tree, but their effective location is controlled by Podman's volume_path setting in containers.conf. podman info reports the resulting volumePath, so use that instead of assuming <graphroot>/volumes.

Confirm the active directory:

bash
podman info --format 'volumePath={{.Store.VolumePath}}'
output
volumePath=/var/lib/containers/storage/volumes

Use volume names in run commands and Compose files — not hard-coded _data paths.


Find where a specific image is stored

Podman images are not single .tar files on disk. Overlay stores them as layered directories under graphroot.

Inspect the graph driver data for one image:

bash
podman image inspect docker.io/library/alpine:3.20 --format '{{json .GraphDriver}}'

Sample output:

output
{"Name":"overlay","Data":{"UpperDir":"/var/lib/containers/storage/overlay/08bc4e534116aa76b16015484b82eac51f9a593416feae9296c8a2d4bb7aa4a2/diff","WorkDir":"/var/lib/containers/storage/overlay/08bc4e534116aa76b16015484b82eac51f9a593416feae9296c8a2d4bb7aa4a2/work"}}

The hash-named directories under /var/lib/containers/storage/overlay/ are storage internals. Understanding that layers live under graphroot is enough — do not rename or delete them manually.


Find where a Podman container is stored

A running container adds a writable upper layer on top of image layers. Create a short-lived container to inspect:

bash
podman run -d --name storage-demo docker.io/library/alpine:3.20 sleep 600

Read the overlay paths Podman assigned:

bash
podman inspect storage-demo --format '{{json .GraphDriver}}'

Output includes LowerDir, UpperDir, MergedDir, and WorkDir under graphroot:

output
{"Name":"overlay","Data":{"LowerDir":"/var/lib/containers/storage/overlay/.../diff","MergedDir":"/var/lib/containers/storage/overlay/.../merged","UpperDir":"/var/lib/containers/storage/overlay/.../diff","WorkDir":"/var/lib/containers/storage/overlay/.../work"}}

Remove the container with podman rm -f storage-demo when you finish — not by deleting overlay-containers/ directories. Manual deletion can leave storage metadata inconsistent.


Find where a Podman volume is stored

Create a named volume, then ask Podman where it landed:

bash
podman volume create storage-demo-vol

Inspect the volume to read its Mountpoint:

bash
podman volume inspect storage-demo-vol

Read the Mountpoint field:

output
[
     {
          "Name": "storage-demo-vol",
          "Driver": "local",
          "Mountpoint": "/var/lib/containers/storage/volumes/storage-demo-vol/_data",
          ...
     }
]

That path helps you verify backups or disk allocation. Application definitions should still reference the volume name storage-demo-vol, not the _data directory.


Which storage.conf file does Podman use?

Podman reads storage.conf from a precedence chain. The comments in the shipped file list:

text
/usr/containers/storage.conf
/etc/containers/storage.conf
$HOME/.config/containers/storage.conf
$XDG_CONFIG_HOME/containers/storage.conf

On RHEL 10 the vendor file lives at /usr/share/containers/storage.conf. The lab host has no /etc/containers/storage.conf, so Podman uses the vendor file directly:

bash
podman info --format 'configFile={{.Store.ConfigFile}}'
output
configFile=/usr/share/containers/storage.conf

Important behavior: the highest-precedence storage.conf that applies replaces the effective configuration. Podman does not inherit individual unset fields from lower-precedence files. If you drop a [storage.options] block in a user override, you lose those options entirely unless you repeat them.

Rootful configuration

System administrators normally add overrides at /etc/containers/storage.conf. That file does not exist on a fresh lab install, which is why podman info points at /usr/share/containers/storage.conf.

Rootless configuration

Rootless Podman reports the user config path even before the file exists:

bash
su - podstore -c 'podman info --format configFile={{.Store.ConfigFile}}'
output
configFile=/home/podstore/.config/containers/storage.conf

Create ~/.config/containers/storage.conf to override rootless storage. Do not edit /etc/containers/storage.conf for a single user's rootless store unless you intend a system-wide change.


Understand the overlay storage driver

Confirm the active driver:

bash
podman info --format 'graphDriverName={{.Store.GraphDriverName}}'
output
graphDriverName=overlay

OverlayFS presents a merged view to the container:

text
lower layers → image layers (read-only)
upper layer  → container writable changes
merged view  → filesystem seen inside the container

Read driver capabilities from podman info:

bash
podman info --format '{{json .Store.GraphStatus}}'

Rootful sample output on the lab host:

output
{"Backing Filesystem": "xfs", "Native Overlay Diff": "false", "Supports d_type": "true", "Supports shifting": "true", "Supports volatile": "true", "Using metacopy": "true"}

Backing Filesystem shows the filesystem under graphroot. Native Overlay Diff, Using metacopy, and related flags describe overlay behavior on this kernel — not something you need to tune unless troubleshooting driver performance.


Native rootless OverlayFS vs fuse-overlayfs

Rootless Podman needs a kernel that supports rootless overlay. Kernels before 5.12.9 do not support native rootless OverlayFS. The lab kernel is well above that threshold:

bash
uname -r
output
6.12.0-211.47.1.el10_2.x86_64

Compare rootful and rootless overlay behavior:

bash
podman info --format '{{json .Store.GraphStatus}}'

Rootful on the lab host reports "Native Overlay Diff": "false" with overlay.mountopt set to nodev,metacopy=on.

As rootless user podstore:

bash
su - podstore -c 'podman info --format "{{json .Store.GraphStatus}}"'
output
{"Backing Filesystem": "xfs", "Native Overlay Diff": "true", "Supports d_type": "true", "Supports shifting": "false", "Supports volatile": "true", "Using metacopy": "false"}

Rootless Podman here uses native kernel OverlayFS — no fuse-overlayfs helper.

When fuse-overlayfs still matters

fuse-overlayfs remains relevant when:

  • the kernel lacks rootless overlay support
  • the backing filesystem cannot use native overlay
  • you explicitly set mount_program in storage.conf

The default RHEL storage.conf leaves it commented:

text
#mount_program = "/usr/bin/fuse-overlayfs"

Do not install fuse-overlayfs on every modern host. Check graphOptions and graphStatus in podman info first.


Configure fuse-overlayfs when required

Add this only when podman info shows native overlay is unavailable or unsuitable:

toml
[storage.options.overlay]
mount_program = "/usr/bin/fuse-overlayfs"

Place the snippet in the storage.conf that matches your scope — /etc/containers/storage.conf for rootful, ~/.config/containers/storage.conf for rootless.

If a rootless storage.conf was created on an older kernel, automatic driver selection may differ from a clean install on a current system. After any change, verify with podman info before pulling images.


Change Podman's graphroot

Relocating graphroot is common when /var is tight and a larger partition is mounted at /data. Start from the live values:

bash
podman info --format 'graphRoot={{.Store.GraphRoot}}'

Example override in /etc/containers/storage.conf or ~/.config/containers/storage.conf:

toml
[storage]
driver = "overlay"
graphroot = "/data/containers/storage"

Changing graphroot alone does not move existing images, containers, or volumes. Podman looks at the new directory — which starts empty unless you migrate content.

Before editing:

  1. Stop running containers.
  2. Inventory images and volumes you need to keep.
  3. Back up important named volumes and export critical images if required.
  4. Copy or rsync the old graphroot to the new path, or accept starting fresh at the new location.

A typical rootful migration copies /var/lib/containers/storage to the new path, updates storage.conf, relabels SELinux (next section), and restarts Podman workloads. Test on a non-production host first.


SELinux when moving graphroot

On SELinux-enforcing RHEL-family hosts, a new graphroot needs the same labeling semantics as the default store. The shipped storage.conf documents the pattern:

bash
sudo semanage fcontext -a -e /var/lib/containers/storage /data/containers/storage

Apply labels to the new tree:

bash
sudo restorecon -R -v /data/containers/storage

Skipping relabel after moving graphroot often produces permission failures that look like generic volume errors — see Fix Podman volume permission denied for mount-level diagnosis, but fix labeling here when the root cause is a relocated store.


Store images separately with imagestore

When only image cache size is the problem, imagestore splits new image storage from container writable content:

toml
[storage]
graphroot = "/var/lib/containers/storage"
imagestore = "/mnt/container-images"

Rules:

  • imagestore must differ from graphroot
  • newly pulled or stored images land in imagestore
  • existing images already under graphroot remain accessible with overlay storage
  • container writable layers and volumes stay under graphroot

This suits a layout where a large partition holds image layers and a smaller fast disk holds active container state. It is more precise than moving the entire Podman store when images alone dominate disk use.

After configuring imagestore, confirm paths in podman info. When no separate store is configured, imageStore in JSON output reports an image count rather than a filesystem path.


imagestore vs additionalimagestores

imagestore additionalimagestores
Role writable destination for newly stored images additional image sources
Count one configured path list of paths
Typical use split image cache onto another partition shared read-only image repositories
Writable yes — new images go here generally read-only

Example read-only additional store:

toml
[storage.options]
additionalimagestores = [
  "/mnt/shared-container-images"
]

additionalimagestores must point at an existing containers/storage image store; it is not a directory for saved image tarballs. Populate that store with tooling that understands the containers/storage layout, then expose it read-only to consumers. Images from read-only stores appear alongside local images when you run podman images. Do not treat additionalimagestores as a second writable cache.


Rootless Podman storage on NFS

NFS, Lustre, and GPFS or Spectrum Scale cannot host rootless Podman graphroot because they do not understand user namespaces. A common failure mode is $HOME on NFS, which places the default rootless store at:

text
$HOME/.local/share/containers/storage

Fix by pointing rootless storage at local disk in ~/.config/containers/storage.conf:

toml
[storage]
driver = "overlay"
graphroot = "/local/storage/podstore/containers"
runroot = "/run/user/1000/containers"

Use a path on a local filesystem the user can write. This addresses Podman's storage backend — not bind-mounting application data from NFS into a container, which is a separate topic.


rootless_storage_path vs per-user graphroot

rootless_storage_path is a storage.conf setting that administrators can use to change the default rootless storage location for users. It is especially useful when home directories are on NFS but local storage is available elsewhere.

A per-user graphroot= in ~/.config/containers/storage.conf can override the effective rootless store for that user. After any change, verify the result with podman info.

Environment variables such as XDG_DATA_HOME shift the default rootless location before any user storage.conf exists. Once you set graphroot explicitly, that setting wins — verify with:

bash
podman info --format 'graphRoot={{.Store.GraphRoot}}'

transient_store: what it actually does

transient_store changes where container metadata is kept, not whether all layer files vanish on reboot:

toml
[storage]
transient_store = true

With transient_store enabled, container metadata uses temporary storage under runroot. That can improve some performance characteristics but metadata is lost on reboot. Persistent layer files may still exist on disk under graphroot, which is why stale storage can accumulate.

Upstream containers/storage documentation recommends leaving transient_store disabled for normal installations. If you enable it and reboot, external cleanup may be required — podman system prune --external removes storage not referenced by current metadata. Do not enable this as a generic performance tweak.

The lab host reports:

bash
podman info --format 'transientStore={{.Store.TransientStore}}'
output
transientStore=false

Check Podman storage usage

For a quick space summary:

bash
podman system df

Sample output:

output
TYPE           TOTAL       ACTIVE      SIZE        RECLAIMABLE
Images         25          1           758.1MB     758.1MB (100%)
Containers     1           1           233.6kB     0B (0%)
Local Volumes  10          1           11B         11B (100%)

This shows how much space images, containers, and volumes consume. When the filesystem is full and pulls fail with no space left on device, use the dedicated no-space troubleshooting guide — this article covers where data lives, not emergency recovery.


Do not manually delete Podman storage directories

Do not solve cleanup by removing directories such as:

text
overlay/
overlay-containers/
overlay-images/
volumes/

or storage database files under graphroot. Manual deletion can desynchronize Podman's metadata from on-disk layers.

Use podman rm, podman rmi, podman volume rm, and podman system prune for normal lifecycle work. If metadata is already inconsistent, follow the storage corruption reset procedure — not hand-editing overlay/ trees.


Storage configuration quick reference

Goal Setting or command
see current storage paths podman info
inspect one volume path podman volume inspect
primary persistent store graphroot
runtime or temp store runroot
move new image storage separately imagestore
attach read-only image stores additionalimagestores
custom rootless local store rootless graphroot in ~/.config/containers/storage.conf
use FUSE overlay helper mount_program under [storage.options.overlay]
temporary metadata mode transient_store
check disk consumption podman system df

References


Summary

Podman stores images and container layers according to storage.conf, while the default named-volume location is reported as volumePath and can be configured separately through containers.conf. On the lab host, rootful Podman uses graphroot /var/lib/containers/storage and runroot /run/containers/storage from its storage configuration, while Podman reports volumePath /var/lib/containers/storage/volumes for named volumes. Rootless user podstore stores under ~/.local/share/containers/storage with runtime state in /run/user/1016/containers, using native OverlayFS on kernel 6.12 without fuse-overlayfs.

When you need to relocate storage, edit the correct storage.conf for your scope, migrate existing content if you want to keep it, and relabel SELinux on RHEL-family systems. Use imagestore when only the image cache needs a larger partition, and additionalimagestores for read-only shared image content — not as interchangeable writable caches. Rootless graphroot must sit on a local filesystem that supports user namespaces; NFS home directories require a local override.

For volume mount permission errors at an existing path, see Fix Podman volume permission denied. For creating and backing up named volumes, see Podman volumes.


Frequently Asked Questions

1. Where does Podman store images on Linux?

Ask the running installation with podman info. Rootful Podman on RHEL-family hosts typically uses graphroot /var/lib/containers/storage for image layers and metadata. Rootless users usually store under ~/.local/share/containers/storage unless storage.conf overrides graphroot. Defaults vary by distribution and configuration.

2. What is the difference between graphroot and runroot in Podman?

graphroot is the primary containers/storage location for images, container layers, and metadata. Named volumes commonly live beneath it by default, but Podman's volume_path setting can place them elsewhere. runroot holds temporary runtime storage.

3. Which storage.conf file does rootless Podman use?

Rootless Podman reads $HOME/.config/containers/storage.conf when present, or $XDG_CONFIG_HOME/containers/storage.conf if XDG_CONFIG_HOME is set. A higher-precedence storage.conf replaces the entire effective configuration; it does not merge missing fields from lower files. podman info reports the active configFile path.

4. Can rootless Podman use NFS for graphroot?

No. Podman documentation states that NFS, Lustre, and GPFS or Spectrum Scale do not support rootless container storage because they do not understand user namespaces. When home is on NFS, point rootless graphroot at a local filesystem path in the user storage.conf instead.

5. What does imagestore do in storage.conf?

imagestore sends newly pulled or stored images to a separate filesystem path while container writable content remains in graphroot. It must differ from graphroot. Existing images already under graphroot remain accessible. Use it when image cache size is the bottleneck, not when you only need to move all Podman data.
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)