Optimizing Podman on the image and storage side: layer caching, multi-stage builds, zstd:chunked partial pull, composefs, garbage collection, image pruning, and overlayfs I/O tuning so containers are lighter and faster.

In episode 17 you bound containers to the operating system via systemd and Quadlet: socket activation, auto-update, and rollback. Episode 18 steps down one level from the service lifecycle to something that determines the operational cost of containers: performance and efficiency. We cover two large domains — image efficiency and storage tuning — whose impact is felt directly in pull time, build time, disk usage, and runtime I/O.
A container image is a chain of layers. The fewer and smaller the layers that must be downloaded and stored, the faster the pull and the more disk you save. There are four main techniques you need to know.
When podman build runs, each instruction in the Containerfile produces a new layer. Layers that haven't changed between two builds don't need to be rebuilt — the result is taken from the cache. The principle is simple: instructions that change often are placed at the end of the Containerfile, while parts that rarely change (like dependency installation) are placed at the beginning:
FROM node:22
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
CMD ["npm", "start"]With the order above, changes to application code (COPY . .) don't force a dependency reinstall because the npm ci layer still matches the cache. Conversely, if COPY . . were placed before npm ci, every code change would destroy the cache and trigger an expensive reinstall.
A production image doesn't need the build toolchain like compilers, SDKs, or package managers. Multi-stage builds separate the build stage from the runtime stage, then copy only the needed artifacts:
FROM golang:1.24 AS builder
WORKDIR /app
COPY . .
RUN CGO_ENABLED=0 go build -o server .
FROM gcr.io/distroless/base-debian12
COPY --from=builder /app/server /server
EXPOSE 8080
CMD ["/server"]The first stage produces the binary, the second starts from a small image and only takes the binary via COPY --from=builder. The result: an image can shrink from hundreds of megabytes to tens of megabytes — savings immediately visible on the first pull and on disk usage.
Images can be compressed with the zstd:chunked format, which splits the image into small indexed chunks. When the image is updated, only the changed chunks are downloaded, not the entire image. This is what's called a partial pull, and it can be enabled at build:
podman build --compression-format zstd:chunked -t myapp .The benefits of zstd:chunked are felt especially in CI/CD environments and registries holding frequently updated images. Note that pull support on the client side must be available for a partial pull to actually happen; without that support the image can still be pulled in full, just not as efficiently as expected.
composefs is a filesystem-based storage layout being developed by the containers team: images are stored as OCI file trees that can be deduplicated across containers, enabling data sharing at the filesystem level and stronger content verification. Since it's still on the roadmap and only opt-in in the latest versions, you don't need to configure it now — but keep an eye on it, because composefs has the potential to significantly reduce data duplication and speed up container startup.
Containers are hungry disk consumers: unused images, stale build cache, and settled layers. The three areas that must be routinely managed are garbage collection, image pruning, and overlayfs I/O optimization.
containers/storage has an automatic garbage collection mechanism to discard layers no longer referenced. You don't have to trigger it manually, but it's important to understand that GC only works on data that's truly unused. To monitor the space being used:
podman system df
podman system df -vThe podman system df output shows the size of images, containers, volumes, and build cache. A ballooning cache column is a strong sign it's time to clean up.
Routine cleanup is a mandatory habit on production and development machines. podman image prune removes images not used by any container, while podman system prune covers more:
podman image prune -a
podman system prune --all --volumes
podman rmi -f $(podman images -qf dangling=true)Note the difference: podman image prune -a removes all images without a container, while podman system prune --all --volumes also cleans stopped containers, build cache, and unused volumes. The last line uses podman images -qf to remove dangling images (those that lost their tags) in one go — but be careful, because force-removing images still in use can break running containers.
Podman's default storage driver is overlayfs. For I/O-heavy workloads, tuning mount options can reduce metadata overhead. The general settings live in [storage.options.overlay] in storage.conf:
[storage]
driver = "overlay"
[storage.options.overlay]
mountopt = "nodev,metacopy=on"metacopy=on speeds up operations like chmod and chown on large layers because metadata is handled without copying the entire file. On rootless environments with older kernels, fuse-overlayfs becomes the choice — a bit slower than native overlayfs, but still better than exposing storage to the container as an I/O-draining volume. Also make sure you don't let logs and caches pile up inside the container, since that means rewriting to the expensive writable layer.
Here's a compact map you can use as a reference:
| Technique | Goal | How to apply |
|---|---|---|
| Layer caching | Build time | Put rarely changing instructions early in the Containerfile |
| Multi-stage build | Image size | Separate build and runtime stages, copy artifacts with COPY --from |
zstd:chunked | Pull time | Build with --compression-format zstd:chunked |
| composefs | Storage deduplication | Roadmap, watch the latest versions |
| Image pruning | Disk usage | podman system prune --all --volumes routinely |
| Overlayfs tuning | Runtime I/O | Set metacopy=on in [storage.options.overlay] |
The best priority is usually to start with multi-stage builds and layer caching, since both deliver the biggest savings with the least effort.
Tip
Make cleanup a routine, not an emergency action. Run podman system prune --all on a regular schedule on CI or development machines, and set up a disk alert that monitors the output of podman system df so disk problems are detected before they cause failures.
In episode 18 you learned how to control image costs via layer caching, multi-stage builds, zstd:chunked, and the composefs roadmap direction; and how to keep storage efficient via garbage collection, image pruning, and overlayfs I/O tuning.
The key points to take home:
podman system prune beats an emergency.zstd:chunked and composefs are Podman's efficiency direction going forward.In the next episode, Episode 19, you'll move from Linux machines to your own machine: Podman Machine & Desktop — how to run Podman on macOS and Windows via a virtual machine, choose the right provider, and manage everything from the Podman Desktop GUI.