| 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 on the lab host; same flags work rootless without sudo |
| Scope | Turning a build context and Containerfile into an image with podman build — context rules, -f, -t, --pull, layer cache, .containerignore, --build-arg, build secrets, squash, Git/URL contexts, rootless builds, and storage-error routing. Does not cover full Containerfile syntax, multi-architecture workflows, registry push, Buildah tutorials, or image-management commands. |
| Related guides | Pull images with podman pull Install Podman on RHEL Rootless Podman |
podman build reads a Containerfile (or Dockerfile) and the files in a build context, then produces a container image you can run or push. This page focuses on the build command and process. Containerfile instruction reference and multi-architecture publishing have their own lessons.
Build your first image with Podman
Create a small directory with an HTML file and a Containerfile:
podman-build-demo/
├── Containerfile
└── index.htmlPut this in Containerfile:
FROM registry.access.redhat.com/ubi9/ubi-minimal:latest
COPY index.html /opt/index.html
CMD ["/bin/cat", "/opt/index.html"]Change into the directory and build with the current folder as context:
cd podman-build-demo && podman build -t podman-build-demo .Sample output:
STEP 1/3: FROM registry.access.redhat.com/ubi9/ubi-minimal:latest
STEP 2/3: COPY index.html /opt/index.html
--> 70d305736a97
STEP 3/3: CMD ["/bin/cat", "/opt/index.html"]
COMMIT podman-build-demo
--> c10a200289a9
Successfully tagged localhost/podman-build-demo:latest
c10a200289a945750af615f7cd3728c744d12f32dc32da8ae8874ceac83b66e8The general syntax is podman build [options] BUILD_CONTEXT. The trailing . means use the current directory as the build context.
Confirm the image exists:
podman images localhost/podman-build-demoSample output:
REPOSITORY TAG IMAGE ID CREATED SIZE
localhost/podman-build-demo latest c10a200289a9 2 seconds ago 109 MBRun it once to prove the build produced a working image:
podman run --rm podman-build-demoSample output:
<!DOCTYPE html>
<html><body><h1>Podman build demo</h1></body></html>Understand the Podman build context
The build context is the collection of files Podman makes available to instructions such as COPY and ADD. When you run:
podman build .Podman uses the current directory as the build context, excluding files matched by .containerignore or .dockerignore. Files on the host outside that directory are invisible to the build, even if the path looks close.
Create a Containerfile that tries to copy from above the context:
FROM registry.access.redhat.com/ubi9/ubi-minimal:latest
COPY ../outside-context.txt /opt/outside.txtBuild from a subdirectory that does not include the parent file:
podman build context-demo/Sample output:
STEP 1/2: FROM registry.access.redhat.com/ubi9/ubi-minimal:latest
STEP 2/2: COPY ../outside-context.txt /opt/outside.txt
Error: building at STEP "COPY ../outside-context.txt /opt/outside.txt": checking on sources under "/root/podman-build-demo/context-demo": possible escaping context directory error: copier: stat: "/outside-context.txt": no such file or directoryThe file may exist on the host, but it is outside the context boundary. Move the file into the context directory, widen the context path (for example podman build .. from a child folder), or use a build stage that fetches remote content instead of COPY ../.
Use a different Containerfile with -f
By default, Podman looks for Containerfile or Dockerfile in the build context. To use another filename, pass -f:
podman build -f Containerfile.prod -t demo:prod .The -f path points at the recipe; the final . still sets the build context. Those are separate ideas — you can keep production and development Containerfiles in the same directory while sharing one context tree.
Podman can also read a Containerfile from stdin for generated or piped recipes, but local files with -f cover most workflows. Instruction syntax and best practices live in the Podman Containerfile guide.
Name and tag the built image
-t assigns a name and optional tag to the result:
podman build -t localhost/demo:v1 .Sample output ends with:
Successfully tagged localhost/demo:v1List the result:
podman images localhost/demoSample output:
REPOSITORY TAG IMAGE ID CREATED SIZE
localhost/demo v1 c10a200289a9 30 seconds ago 109 MBLocally built images are commonly stored under the localhost/ prefix. You can add more names later with podman tag; listing, retagging, and cleanup are covered in Manage images with podman images.
Control base-image pulling
--pull decides whether Podman contacts the registry for the FROM image. On Podman 5.8.2 the default is missing.
| Policy | Behavior |
|---|---|
always |
Pull the base image from the registry |
missing |
Pull only when the base image is absent locally |
never |
Use the local base image only |
newer |
Pull when registry content is newer than the local copy |
Force a fresh base pull:
podman build --pull=always -t podman-build-demo .--pull controls the base image from the registry. It is separate from build-layer cache, which the next section covers.
Understand build cache and --no-cache
Podman caches intermediate layers. Rebuild the same Containerfile without changes and cached steps are reused:
podman build -t podman-build-demo .Sample output:
STEP 2/3: COPY index.html /opt/index.html
--> Using cache 70d305736a9787923be37270c318e675627634c35d3ce75cf6fe801cdba65c84
--> 70d305736a97
STEP 3/3: CMD ["/bin/cat", "/opt/index.html"]
--> Using cache c10a200289a945750af615f7cd3728c744d12f32dc32da8ae8874ceac83b66e8The Using cache lines show Podman skipped work because the instruction inputs did not change.
Disable layer cache when you need every step to run again:
podman build --no-cache -t podman-build-demo .--no-cache does not automatically pull the newest base image. When you need both a fresh base and a full rebuild:
podman build --pull=always --no-cache -t podman-build-demo .Exclude files with .containerignore
.containerignore keeps files out of the build context. Create one beside your Containerfile:
.git
logs/
*.tmp
secrets/Files matching those patterns are excluded from the context. That shrinks what COPY and ADD can see and prevents accidental copies of build-only material. Podman also reads .dockerignore; when both exist, .containerignore takes precedence.
Build a directory that copies everything except ignored patterns:
podman build ignore-demo/Inside the image, only non-ignored files appear:
STEP 3/3: RUN ls -la /opt/app/
total 8
drwxr-xr-x. 2 root root 21 Aug 22 16:48 .
drwxr-xr-x. 1 root root 17 Aug 22 16:48 ..
-rw-r--r--. 1 root root 8 Aug 22 16:48 keep.txtsecret.tmp matched *.tmp and never entered the context. Try to COPY an ignored file and the build fails:
podman build ignore-fail/Sample output:
Error: building at STEP "COPY hidden.tmp /opt/hidden.tmp": no items matching glob "/root/podman-build-demo/ignore-fail/hidden.tmp" copied (1 filtered out using /root/podman-build-demo/ignore-fail/.containerignore): no such file or directoryPass build arguments with --build-arg
ARG supplies build-time values. Example Containerfile:
FROM registry.access.redhat.com/ubi9/ubi-minimal:latest
ARG APP_VERSION=unset
RUN echo "$APP_VERSION" > /version.txt
CMD ["/bin/cat", "/version.txt"]Pass the value at build time:
podman build --build-arg APP_VERSION=1.2.0 -t demo:arg .Verify it landed in the image:
podman run --rm demo:argSample output:
1.2.0ARG provides values to build instructions and does not automatically become a runtime environment variable. ENV persists in the resulting image configuration and is available to running containers. Do not pass secrets with --build-arg; use build secrets instead. Deeper ARG vs ENV patterns belong in the Podman Containerfile guide. For many arguments, --build-arg-file loads key/value pairs from a file instead of repeating flags.
Use build secrets without storing them in image layers
Build secrets let a RUN step read sensitive files without baking them into a layer. Create secret.txt on the host and reference it at build time:
podman build --secret id=mysecret,src=secret.txt -t secret-demo .Containerfile excerpt:
FROM registry.access.redhat.com/ubi9/ubi-minimal:latest
RUN --mount=type=secret,id=mysecret \
test -s /run/secrets/mysecret && echo "secret available during build"Sample build output includes:
secret available during build/run/secrets/mysecret is mounted only for that RUN instruction — it is not copied into the image filesystem and is not available to later build steps or to podman run unless you explicitly write the secret into a layer, which you should avoid.
The secret file on the host is not sent with a plain COPY. Changing a secret does not always invalidate cached layers that consumed it, so do not treat secret content as a cache key. Runtime secret management for running containers is a separate topic — see Podman secrets.
Squash image layers
--squash collapses newly built layers into one:
podman build --squash -t demo:squashed .Compare layer history before squashing:
podman history localhost/demo:v1Sample output:
ID CREATED CREATED BY SIZE
70d305736a97 51 seconds ago /bin/sh -c #(nop) CMD ["/bin/cat", "/opt/i... 0B
<missing> 51 seconds ago /bin/sh -c #(nop) COPY file:d106c7d06bbd54... 2.56kB
591c6dfb4400 4 days ago /bin/sh -c #(nop) LABEL "org.opencontainer... 109MBAfter a squashed build:
podman history demo:squashedSample output:
ID CREATED CREATED BY SIZE
dbe7b0f93e65 Less than a second ago /bin/sh -c #(nop) CMD ["/bin/cat", "/opt/i... 2.56kB
591c6dfb4400 1 second ago /bin/sh -c #(nop) COPY file:d106c7d06bbd54... 0BSquashing flattens your new layers into one step above the base. --squash-all can include inherited base layers as well. Flattening can make later cache reuse less efficient, so treat squash as a deliberate trade-off, not a default optimization.
Build from a Git repository or URL
The build context does not have to be a local directory. Podman accepts Git repositories, HTTP archives, and remote Containerfile URLs. Build from a path inside a public Git repository:
podman build -t git-demo https://github.com/containers/podman.git#HEAD:contrib/helloSample output ends with:
Successfully tagged localhost/git-demo:latest
e28da069ec7e79fc064668c4b6449e62be0149f963fa3890e7036e3b18b35a9cThe #REF:SUBDIR suffix selects the Git ref and subdirectory. URL contexts let Podman clone the source automatically. Pin a commit or immutable tag when you need reproducible builds — HEAD moves and is not a fixed reference.
Build images as a rootless user
Podman supports rootless image builds with the same command — no sudo:
podman build -t podman-build-demo .Images land in that user's Podman storage. A rootful podman images on the same host does not list another user's builds.
Check whether your session is rootless:
podman info --format "rootless={{.Host.Security.Rootless}}"Sample output on the lab host (root session):
rootless=falseRun the same build as an unprivileged user to store images under that account. Subuid/subgid setup and permission troubleshooting belong in Rootless Podman.
Fix podman build storage errors
When local Podman storage is full, builds fail with errors such as no space left on device during a COPY or COMMIT step. Check what is consuming space:
podman system dfSample output:
TYPE TOTAL ACTIVE SIZE RECLAIMABLE
Images 17 2 109.7MB 109.6MB (100%)
Containers 0 0 0B 0B (0%)
Local Volumes 9 0 11B 11B (100%)podman system df shows where reclaimable space lives. Full recovery — pruning images, growing storage, or resetting the graph root — is covered in Fix Podman no space left on device.
podman build vs Buildah
podman build uses the same underlying build stack as Buildah. For a standard Containerfile-to-image workflow, podman build is usually enough. Buildah adds a dedicated image-building CLI with finer-grained step control (buildah from, buildah run, buildah commit) for pipelines that assemble images without a single podman build invocation. Choose Buildah when you need that lower-level workflow; choose Podman when build is one step in a broader container workflow.
Multi-architecture builds
Podman can target multiple platforms in one build:
podman build --platform linux/amd64,linux/arm64 --manifest IMAGE .If the Containerfile contains RUN instructions for a foreign CPU architecture, the host needs compatible emulation (such as QEMU) or a native builder for that architecture. Full manifest lists, --manifest, and publishing multi-architecture images are covered in Create multi-architecture images with Podman.
Common podman build errors
| Symptom | Likely cause | Where to go |
|---|---|---|
possible escaping context directory or no such file or directory on COPY |
File outside build context or blocked by .containerignore |
Widen context path or move file into context |
Using cache when you expected a rebuild |
Unchanged instructions and layer cache | --no-cache or change inputs |
| Stale base image despite rebuild | Local base image reused | --pull=always or --pull=newer |
References
Summary
podman build turns a build context and Containerfile into a local image. You created podman-build-demo, built with . as the context, tagged the result, and ran it to confirm the HTML file was baked in. The context boundary is the main footgun: only files inside the context directory reach COPY and ADD, and .containerignore can hide files on purpose.
Pull policies (--pull) govern the base image from a registry; layer cache and --no-cache govern whether build steps rerun. Combine --pull=always with --no-cache when you need both a fresh base and a full rebuild. Build args pass temporary values; build secrets mount sensitive files only for specific RUN steps without storing them in layers.
Squashing flattens new layers at the cost of cache reuse. Rootless users build with the same command into their own storage. When builds fail on disk space, podman system df is the first diagnostic step. For Containerfile instruction details, open the Containerfile guide; for tags, digests, and cleanup after the build, see the image-management lesson.

