Self-hosted service
podman-container-tools/buildah avatar
podman-container-tools/buildah

Buildah: building OCI images from a script instead of a Dockerfile

A tool that facilitates building OCI images.

9,185 stars950 forksGoApache-2.0

At a glance

What is it?
Buildah is a fork-exec image builder with no daemon and no root requirement, whose commands mirror Dockerfile instructions one for one. What it trades away is Podman's long lived container model, and Podman containers stay invisible from Buildah for exactly that reason.
Who is it for?
Buildah fits anyone automating image creation from a shell or from Go, especially where root is unavailable or no daemon can run, and it does not fit a workflow that expects containers to keep running or to be visible to Podman. Before pinning a version, check which release line the pin belongs to, and before building from source, remember the tag set is decided by probing the host.
Can I use it commercially?
Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 1 day ago.
What is it written in?
Mainly Go, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on November 14, 2026, and from our analysis. They are not legal advice.

From daily snapshots

Star history

  • podman-container-tools/buildah9,185 ★ · +156
Star history
Repository2026-09-222026-11-14
podman-container-tools/buildah9k9.2k

156 Stars in the last 53 days, measured from the daily GitHub snapshot. Compare with other projects

Editorial analysis

The example script turns six buildah calls into an image

The shortest complete build in the repository is examples/lighttpd.sh, and it is worth reading call by call because each one maps onto something you would otherwise write as a Dockerfile keyword.

bash
$ cat > lighttpd.sh <<"EOF"
#!/usr/bin/env bash

set -x

ctr1=$(buildah from "${1:-fedora}")

## Get all updates and install our minimal httpd server
buildah run "$ctr1" -- dnf update -y
buildah run "$ctr1" -- dnf install -y lighttpd

## Include some buildtime annotations
buildah config --annotation "com.example.build.host=$(uname -n)" "$ctr1"

## Run our server and expose the port
buildah config --cmd "/usr/sbin/lighttpd -D -f /etc/lighttpd/lighttpd.conf" "$ctr1"
buildah config --port 80 "$ctr1"

## Commit this container to an image name
buildah commit "$ctr1" "${2:-$USER/lighttpd}"
E

Seven buildah calls carry the whole script. `buildah from` creates the working container and the shell variable captures its name, while the first positional argument picks the base image and the second picks the output image name. Two `buildah run` calls install the packages, one `buildah config` call stamps a build host annotation, two more set the command line and the exposed port, and `buildah commit` turns the working container into an image.

What is absent is as informative as what is there. There is no FROM, no RUN, no CMD and no EXPOSE keyword anywhere, and no docker or podman invocation. Tracing comes from `set -x` in bash rather than from a builder.

A Buildah container is a staging area for content, not a service

Podman allows users to create traditional containers where the intent of these containers is to be long lived, while Buildah containers are really just created to allow content to be added back to the container image. That single distinction explains most of the surprising behaviour.

`buildah run` emulates the RUN command in a Dockerfile, while `podman run` emulates `docker run` in functionality. So a container you created with `buildah from` has no intent to stay up, nothing is started for you to attach to, and its life ends when you commit it or delete it. The command inventory is built around that lifecycle: create a working container, mount its root filesystem for manipulation, unmount it, use the updated contents as a filesystem layer to create a new image, then delete a container or an image, or rename a local container.

The consequence for a reader is blunt. Buildah is the wrong tool when the thing you want is a running service, because there is no long lived container here to manage, and reaching for one means going to Podman.

Storage differences keep Podman containers invisible from Buildah

Because of the different concept of a container and the underlying storage differences, you can not see Podman containers from within Buildah or vice versa. That sentence is the practical boundary between the two tools, and it is not a display quirk.

If a team runs containers through Podman for daily work and reaches for Buildah to produce an image, the two views do not merge. A container created on one side is not listed by the other, and no import step in the project's own pages reconciles them. The README sends readers to the Container Tools Guide for the fuller picture, and that is where the storage arrangement gets explained.

For anyone planning a migration or a mixed pipeline, the practical consequence is to treat the two as separate inventory domains and check each with its own command, instead of assuming a container list is shared state.

Dropping the Dockerfile is the feature, and the script becomes the record

Buildah's commands replicate all of the commands that are found in a Dockerfile, which is what allows building images with and without Dockerfiles while not requiring any root privileges. The stated ultimate goal is to provide a lower-level coreutils interface to build images, and the flexibility of building images without Dockerfiles is what allows other scripting languages to be integrated into the build process.

The cost lands on reproducibility. When the build lives in a shell script, the reviewable artefact is the script, and its result depends on the base image you pass in and on the shell it runs under. Nothing in the repository pins a base digest for you, and the demo script takes fedora as the default that any caller can replace through its first positional argument.

The gain is that the same capability can be driven from another language. Buildah is based on an API in Go that can be vendored into other tools, so the shell script is one front end among several rather than the only way in.

The command set maps one to one onto Dockerfile instructions

Images can be built in either the OCI image format or the traditional upstream docker image format, which is the one place Buildah's output surface is broader than a strict OCI-only tool.

The command inventory lines up with what a Dockerfile contains. `buildah from` creates a new working container, either from scratch or using a specified image as a starting point. `buildah copy` copies the contents of a file, URL, or directory into a container's working directory, while `buildah add` adds the contents of a file, URL, or a directory to the container. `buildah config` updates image configuration settings and `buildah commit` creates an image from a working container. `buildah mount` mounts the working container's root filesystem. Inventory and inspection live in `buildah images`, `buildah containers`, `buildah inspect` and `buildah info`, with `buildah pull` and `buildah push` moving images in and out of local storage, and `buildah build` handling instructions from Containerfiles or Dockerfiles.

The gap worth noticing is that no single command covers the whole file. `buildah build` is the only path that takes a Dockerfile, and everything else is the imperative path the demo script uses.

Build tags are probed on the machine that runs make

The Makefile decides which features get compiled by running probe scripts against the host and reading their output. The apparmor tag comes from hack/apparmor_tag.sh, storage tags come from ./btrfs_installed_tag.sh and ./hack/libsubid_tag.sh, and the default tag set adds probes for systemd and sqlite. The security side defaults to seccomp plus the probed apparmor tag.

make
SECURITYTAGS ?= seccomp $(APPARMORTAG)
PREFIX := /usr/local
BINDIR := $(PREFIX)/bin
BASHINSTALLDIR = $(PREFIX)/share/bash-completion/completions
NPROCS := $(shell nproc)
export GO_TEST=$(GO) test -parallel=$(NPROCS)
SELINUXTYPE=container_runtime_exec_t

The consequence is that the binary you end up with is a function of the machine that built it. Build on a host with btrfs present and you get storage support a build elsewhere will not have, and apparmor is decided the same way, while seccomp is always in the default security tag set. EXTRA_BUILD_TAGS is the lever for adding to the set, and the test parallelism comes from nproc, which is host dependent as well.

Install paths, by contrast, are fixed rather than probed: everything lands under /usr/local, binaries in /usr/local/bin, and bash completions in the completions directory beneath it.

The Go module is go.podman.io/buildah, not the repository URL

The import path is not the address of the repository. `go.mod` declares `module go.podman.io/buildah`, while the project itself lives at the podman-container-tools/buildah repository on GitHub. Anyone vendoring the API into their own Go tool imports go.podman.io/buildah, and that is the same API Podman uses when it builds container images via Dockerfiles, which is why Podman can be installed independently of Buildah while still building from files.

The Go line carries its own warning comment: ensure the go and toolchain versions match exactly to prevent unwanted auto-updates, and the file pins a specific go directive. That warning matters for the embedding case rather than for a packaged install, because a mismatch lets the toolchain silently fetch a newer one, which defeats the point of pinning.

The dependency list shows where the boundaries sit. Buildah sits on opencontainers/runc for the runtime, opencontainers/image-spec for the image format, containers/ocicrypt for encryption, moby/buildkit alongside a moby client package, and go.podman.io/storage for storage. Those are the pieces an embedder has to keep compatible.

Two release lines were patched on the same afternoon

Two of the recent releases carry patch version numbers, and the timestamps sit close together: v1.43.5 at 15:41 on 2026-10-12 and v1.45.2 at 15:42 on the same day, followed by v1.46.0 on 2026-10-21. The repository is not archived, and the default branch, main, was last pushed on 2026-11-02.

That pattern says two lines were being patched at once, with the older line's tag appearing about a minute before the newer one. For anyone pinning a version inside a base image or a CI job, the practical question is which line the pin belongs to, because a 1.43.5 pin does not pick up a fix shipped as 1.45.2, and the release list at the repository root is where the pairing is visible rather than inferred.

The remaining reference points sit next to it: a CHANGELOG.md, a developmentplan.md, an install.md, a troubleshooting.md and a docs/tutorials directory.

Editorial conclusion

Buildah fits anyone automating image creation from a shell or from Go, especially where root is unavailable or no daemon can run, and it does not fit a workflow that expects containers to keep running or to be visible to Podman. Before pinning a version, check which release line the pin belongs to, and before building from source, remember the tag set is decided by probing the host.

Frequently asked questions

what is buildah

Buildah is a tool that facilitates building Open Container Initiative container images. It provides a command line tool that can create a working container, either from scratch or from an image, and then turn that container into an image.

What is Buildah used for?

Buildah is used for building OCI images. Its commands replicate the commands found in a Dockerfile, which allows building images with and without Dockerfiles while not requiring any root privileges. Images can be built in either the OCI image format or the traditional upstream docker image format.

Is Buildah daemonless?

Buildah follows a simple fork-exec model and does not run as a daemon. It is based on an API in Go that can be vendored into other tools, and Podman uses that same Go API when it builds container images via Dockerfiles.

Why use Buildah instead of Docker?

The project's own comparison is drawn against Podman rather than Docker. Buildah specializes in building OCI images without Dockerfiles and without root privileges, while Podman specializes in maintaining and modifying images and in running the containers made from them. The output can still be written in the traditional upstream docker image format.

how to install buildah

The README links a separate installation notes file at install.md, plus a troubleshooting guide and a docs/tutorials directory. On the build side the Makefile installs under /usr/local, with binaries in /usr/local/bin and bash completions in /usr/local/share/bash-completion/completions.

Official sources

  1. License: Apache-2.0
  2. podman-container-tools/buildah on GitHub
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/podman-container-tools-buildah.svg)](https://hysenlabs.com/projects/podman-container-tools-buildah)

Star history chart for your README

markdown
[![Star history](https://hysenlabs.com/chart/podman-container-tools-buildah.svg)](https://hysenlabs.com/projects/podman-container-tools-buildah)

Was this useful?