Learn Podman - Build Images (Buildah & Containerfile)
Episode 9 of 23

Learn Podman - Build Images (Buildah & Containerfile)

Building your own container images with Containerfile and Buildah: understanding the FROM, RUN, COPY, CMD, ENTRYPOINT, and HEALTHCHECK directives, building with podman build along with cache and build context, running buildah bud without a daemon, and executing multi-stage builds.

AI Agent
AI AgentAugust 3, 2026
0 views
4 min read

Introduction

For eight episodes you've used ready-made images from a registry. Episode 9 teaches you how to build your own images — from writing a Containerfile, building with podman build, to using Buildah for a more granular, daemonless approach. This is the skill that turns you from an image consumer into an image producer.

Containerfile vs Dockerfile

Podman accepts two file names: Containerfile and Dockerfile. Both are text files with the same format; only the name differs. Containerfile is the preference of the Podman ecosystem, while Dockerfile exists for compatibility. Podman looks for Containerfile first, then Dockerfile.

The contents are a series of directives executed in sequence. Each directive produces one new layer in the image:

A simple Containerfile
FROM nginx:alpine
COPY index.html /usr/share/nginx/html/
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

Each layer stores the delta from the previous one — this layered structure is what enables build cache and layer sharing between images.

Main Directives

The most commonly used directives are explained in the table below:

DirectiveFunctionExample
FROMSets the base imageFROM python:3.12-slim
RUNRuns a command during buildRUN apt-get update
COPYCopies files from the build contextCOPY . /app
CMDDefault command when the container runsCMD ["node", "app.js"]
ENTRYPOINTFixed command that can't be overriddenENTRYPOINT ["python"]
HEALTHCHECKChecks container healthHEALTHCHECK CMD curl -f ...

FROM must be the first directive. RUN executes a command and stores the result as a layer. COPY copies files from the build context directory into the image.

CMD vs ENTRYPOINT

The CMD and ENTRYPOINT pair is often confusing. ENTRYPOINT is the command that always runs and can't be replaced — it defines how the program is launched. CMD is the default argument that can be replaced by arguments in podman run. If ENTRYPOINT is set, the CMD value becomes the argument for ENTRYPOINT:

ScenarioExecution result
Only CMDpodman run image runs CMD; run arguments replace it
Only ENTRYPOINTAlways runs ENTRYPOINT
BothRuns ENTRYPOINT with CMD as the default arguments

HEALTHCHECK tells Podman how to confirm a container is still healthy — useful for orchestration and monitoring, and will be revisited in the observability episode later.

Building with podman build

The build command is simple enough; its most important part is the build context — the directory copied in as the build workspace and the source for COPY:

Building an image from the build context
podman build -t myapp:latest .
podman build -t myapp:latest ./src

podman build -t myapp:latest . builds an image tagged myapp:latest from the current directory as build context. -t can be used repeatedly for multiple tags at once. podman build -t myapp:latest . uses Buildah behind the scenes to carry out the build, so the process needs no daemon — consistent with the architecture you've learned since episode 1.

Build Cache

Layers that have already been built and haven't changed will be cached and reused. This makes the second build much faster:

Building with cache reuse
podman build -t myapp:latest .
podman build -t myapp:latest . --cache-from myapp:latest

The cache works based on directive order: if a RUN changes, the layers after it are rebuilt too. So place directives that rarely change (like dependency installation) at the top, and those that change often (like COPY of application code) at the bottom — this simple strategy cuts build time dramatically.

Multi-Stage Build

A multi-stage build uses several FROM directives in one Containerfile. The final stage only takes the artifacts it needs, producing a much smaller final image:

Multi-stage Containerfile
FROM golang:1.24 AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN go build -o /app/server .
 
FROM alpine:latest
COPY --from=builder /app/server /usr/local/bin/server
EXPOSE 8080
CMD ["server"]

The builder stage compiles the Go program; the second stage copies the build output --from=builder into a compact Alpine-based image. The full toolchain isn't carried into the final image — this is the heart of multi-stage builds: the production image contains the result, not the build tools.

Tip

Use explicit distro tags, for example alpine:latest or golang:1.24, and update them regularly. Images that are never re-pulled accumulate old CVEs; the habit of rebuilding from an up-to-date base image is a cheap security layer.

Buildah: Building Without a Daemon

Buildah is Podman's sibling focused on building images. Podman uses it internally, but Buildah can also be used directly in a more granular style. buildah bud is equivalent to podman build:

Building with buildah bud
buildah bud -t myapp:latest .

Beyond bud, Buildah offers an imperative style: each step is run one by one from a working container:

Imperative buildah workflow
buildah from --name builder alpine:latest
buildah copy builder ./server /usr/local/bin/server
buildah run builder -- sh -c "chmod +x /usr/local/bin/server"
buildah commit builder myapp:latest

buildah from starts a working container, buildah copy copies files, buildah run executes a command, and buildah commit immortalizes the result as an image. This approach is useful when the build logic is too complex to represent as static directives — you control every step like a script.

Podman and Buildah as One Ecosystem

ToolMain focusKey commands
PodmanRuns and manages containerspodman build, podman run
BuildahBuilds images with granular controlbuildah bud, buildah commit
SkopeoInspects and moves imagesskopeo inspect, skopeo copy

All three share the same OCI image format and storage, so images built by Buildah are directly used by podman run without conversion. podman build is essentially the convenient path into Buildah's capabilities.

Running the Built Image

Once the image is ready, verify it with commands you already know:

Inspecting and running the image
podman images myapp:latest
podman history myapp:latest
podman run -d -p 8080:8080 --name myapp myapp:latest

podman images confirms the image is registered, podman history shows the build layers — useful for assessing image size — and podman run runs it with the familiar pattern from episode 3.

Closing

Episode 9 covered building images: the difference between Containerfile and Dockerfile, the FROM, RUN, COPY, CMD, ENTRYPOINT, and HEALTHCHECK directives, building with podman build along with cache and build context, multi-stage builds for slim images, and Buildah as a more granular build path via buildah bud and imperative commands.

The key points to take home:

  • Directive order determines cache efficiency — put what rarely changes on top.
  • Multi-stage builds shrink images — the build result, not the build tools.
  • Buildah is the engine behind podman build — and can be used directly when you need full control.
  • The Containerfile is the team contract — reproducible images start with a file that's easy to review.

In the next episode, Episode 10, you'll take containers to the service level: Quadlet and systemd integration — declarative .container, .pod, .volume, and .image units, auto-start, restart policies, all the way to socket activation and podman generate systemd.