Build Container Images with `podman build`

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:

text
podman-build-demo/
├── Containerfile
└── index.html

Put this in Containerfile:

dockerfile
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:

bash
cd podman-build-demo && podman build -t podman-build-demo .

Sample output:

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
c10a200289a945750af615f7cd3728c744d12f32dc32da8ae8874ceac83b66e8

The general syntax is podman build [options] BUILD_CONTEXT. The trailing . means use the current directory as the build context.

Confirm the image exists:

bash
podman images localhost/podman-build-demo

Sample output:

output
REPOSITORY                   TAG         IMAGE ID      CREATED        SIZE
localhost/podman-build-demo  latest      c10a200289a9  2 seconds ago  109 MB

Run it once to prove the build produced a working image:

bash
podman run --rm podman-build-demo

Sample output:

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:

bash
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:

dockerfile
FROM registry.access.redhat.com/ubi9/ubi-minimal:latest
COPY ../outside-context.txt /opt/outside.txt

Build from a subdirectory that does not include the parent file:

bash
podman build context-demo/

Sample output:

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 directory

The 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:

bash
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:

bash
podman build -t localhost/demo:v1 .

Sample output ends with:

output
Successfully tagged localhost/demo:v1

List the result:

bash
podman images localhost/demo

Sample output:

output
REPOSITORY        TAG   IMAGE ID      CREATED         SIZE
localhost/demo    v1    c10a200289a9  30 seconds ago  109 MB

Locally 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:

bash
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:

bash
podman build -t podman-build-demo .

Sample output:

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 c10a200289a945750af615f7cd3728c744d12f32dc32da8ae8874ceac83b66e8

The 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:

bash
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:

bash
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:

text
.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:

bash
podman build ignore-demo/

Inside the image, only non-ignored files appear:

output
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.txt

secret.tmp matched *.tmp and never entered the context. Try to COPY an ignored file and the build fails:

bash
podman build ignore-fail/

Sample output:

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 directory

Pass build arguments with --build-arg

ARG supplies build-time values. Example Containerfile:

dockerfile
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:

bash
podman build --build-arg APP_VERSION=1.2.0 -t demo:arg .

Verify it landed in the image:

bash
podman run --rm demo:arg

Sample output:

output
1.2.0

ARG 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:

bash
podman build --secret id=mysecret,src=secret.txt -t secret-demo .

Containerfile excerpt:

dockerfile
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:

output
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:

bash
podman build --squash -t demo:squashed .

Compare layer history before squashing:

bash
podman history localhost/demo:v1

Sample output:

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...  109MB

After a squashed build:

bash
podman history demo:squashed

Sample output:

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...  0B

Squashing 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:

bash
podman build -t git-demo https://github.com/containers/podman.git#HEAD:contrib/hello

Sample output ends with:

output
Successfully tagged localhost/git-demo:latest
e28da069ec7e79fc064668c4b6449e62be0149f963fa3890e7036e3b18b35a9c

The #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:

bash
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:

bash
podman info --format "rootless={{.Host.Security.Rootless}}"

Sample output on the lab host (root session):

output
rootless=false

Run 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:

bash
podman system df

Sample output:

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:

bash
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.


Frequently Asked Questions

1. What is the podman build context?

The build context is the set of files available to build instructions such as COPY and ADD. Only files inside the context directory are available. Paths outside that directory, such as COPY ../file, fail even when the file exists on the host.

2. What is the difference between --pull and --no-cache in podman build?

--pull controls whether Podman contacts the registry for the base image. --no-cache forces rebuild of image layers without reusing cached build steps. To refresh both the base image and every layer, combine --pull=always with --no-cache.

3. Does podman build work rootless?

Yes. Rootless users run the same podman build command without sudo. Images are stored in that user Podman storage, so a rootful podman images listing does not show another user builds unless you switch users or use a shared registry.

4. What is the difference between ARG and ENV in a build?

ARG 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.

5. Are build secrets stored in the final image?

No. Build secrets mounted with RUN --mount=type=secret are available only during that build step. They are not intended to appear in image layers or in the running container unless you explicitly copy them, which you should avoid.
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)