buildah-ffi
Rust library and oci-builder CLI that embed Buildah in-process. There is no Buildah daemon and no buildah binary on PATH.
Linux runs the engine. Other targets, including macOS, link a stub with the same ABI so the crate still compiles. The stub returns unsupported.
Install from crates.io
Library:
= "0.1"
CLI:
Both commands compile the crate on your machine. On Linux, build.rs builds the Buildah Go shim and links it into the binary, so the build needs Go 1.26 or newer, a C compiler, and pkg-config. libseccomp is optional: when pkg-config finds it, the seccomp build tag is turned on. On macOS and other non-Linux hosts the stub is linked instead. startup() and oci-builder then exit with code 8 and a message that Buildah is available on Linux only.
An image build also needs the host prerequisites under Build requirements. Rootless use needs user namespaces. A pull needs a signature policy. A RUN instruction with oci or rootless isolation needs runc or crun on PATH. chroot isolation and a scratch image that only uses COPY do not.
Library
Call startup() before spawning threads or parsing arguments. Buildah may re-exec this process for a rootless user namespace, and that child has to reach startup before anything else reads argv.
use ;
shutdown() releases the storage mounts. Dropping the Builder does not.
CLI
policy.json for a local store that does not verify signatures:
Stdout of build and push is image_id=, digest=, and reference=. Logs go to stderr. The exit code is the ErrorCode value. Clap's own usage errors stay exit code 2. diagnose prints the prerequisite report and exits 0 when the host is ready.
The sample Dockerfiles in examples/ live in the source repository. They are not part of the published crates.
Architecture
oci-builder --> buildah-ffi (safe Rust)
|
| C ABI (rob_*.h): borrowed C strings in, malloc'd buffers out
v
Go c-archive
|
+-- buildah.InitReexec / rootless re-exec
+-- containers/storage (one store per process)
+-- imagebuildah.BuildDockerfiles
+-- libimage tag
+-- buildah.Push
Buildah-specific types stay behind the C ABI. The Rust API (Builder::build, Builder::push, Builder::tag) does not expose Go structs, so Buildah's internals can change without an ABI break.
The archive is linked with static:+whole-archive so the Go runtime's .init_array constructor is not dropped. startup is a normal call from main. A Rust constructor would race that Go constructor and can deadlock inside cgo.
Safety
- Call
startup()as the first thing inmain, before spawning threads and before parsing arguments. Storage helpers re-exec this binary and dispatch onargv[0]. Ifstartuphas not run, those children do not reach their handlers. - Rootless startup may re-exec. The parent waits and exits with the child's status. The child's
argv[0]is the original name plus-in-a-user-namespace, and the rest of the arguments are unchanged.startupreturns only in the child (or when no re-exec was required). - Do not call
unshareorstartupwhile holding a lock you expect to survive a fork. The re-exec path runs/proc/self/exe, then the parent exits. - Input strings are borrowed for the call. The shim copies them with
C.GoString. - Output buffers are
malloc'd, NUL-terminated, and owned by the caller.lendoes not include the NUL. A NULL pointer withlen == 0is empty. The safe API frees them before returning. rob_buildah_versionis process-lifetime storage. Do not free it.- Log callbacks run on a Go thread. The bytes are valid only for that call; the safe API copies them first. The callback must not call back into
Builder(the engine lock is held) and must not unwind. Panics in the callback are caught and discarded. - Cancel tokens are integers, not Go pointers.
canceldoes not take the engine lock.Dropfrees the token and does not cancel. A token that is already freed when an operation starts is reported as cancelled, so the token has to outlive the call.Cloneshares one token. - One store is open per process.
Builderis not cloned.Dropdoes not shut the store down; callshutdownso the graph driver can release mounts. A secondopenfails untilshutdownreturns. - Passwords are copied into the Go heap for the push and are not written to logs.
DebugforPushRequestredacts the password. - The Go runtime installs its own signal handlers. Cancellation is the supported way to interrupt a build or push.
MaybeReexecUsingUserNamespace can still os.Exit if user-namespace setup fails after the pre-check. startup checks newuidmap, newgidmap, subordinate IDs, and the user-namespace sysctls first so the common failures return ErrorCode::Prerequisite instead of exiting.
Build requirements
| Requirement | Why |
|---|---|
| Linux, 64-bit | The engine uses cgo, namespaces, and /proc/self/exe. |
| Go >= 1.26 | Buildah v1.45.1 sets go 1.26.0. |
| gcc, libc headers, pkg-config | cgo. |
| libseccomp (optional) | Enables the seccomp tag. Without it, RUN that installs a seccomp profile fails. The library is linked dynamically when the tag is on. |
| libapparmor (optional) | Enables the apparmor tag when pkg-config finds it. |
uidmap (newuidmap, newgidmap) |
Rootless user namespaces. The binaries must be setuid or carry the matching file capability. |
/etc/subuid and /etc/subgid |
Subordinate ranges for the calling user. |
| fuse-overlayfs | Rootless overlay. vfs needs neither overlay nor a mount helper. |
| runc or crun | RUN with oci or rootless isolation. chroot, and a scratch image that only uses COPY, do not need them. |
policy.json |
Pulls fail closed when neither /etc/containers/policy.json nor ~/.config/containers/policy.json exists. |
startup / oci-builder diagnose prints status: ready or status: blocked with [ok], [warn], and [fail] lines. Blocked startup returns exit code 4 and includes that report. Missing overlay, a missing policy, and a missing runc are warnings. A missing newuidmap, a zero user.max_user_namespaces, kernel.unprivileged_userns_clone=0, or kernel.apparmor_restrict_unprivileged_userns=1 is a failure for non-root.
Build tags always include exclude_graphdriver_btrfs, exclude_graphdriver_devicemapper, and containers_image_openpgp (pure-Go signature verification). Image signing is not supported. Extra tags can be added with ROB_GO_TAGS.
Cross-compiling the archive is refused unless CC and ROB_ALLOW_CROSS=1 are set. build.rs runs go build -mod=readonly, so crates/buildah-ffi/shim/go.sum has to be present. Refresh it on Linux (or in a Linux container) with GOOS=linux:
Versions
Direct module requirement:
go.podman.io/buildahv1.45.1 (modulego.podman.io/buildah, repositorypodman-container-tools/buildah)
Versions recorded in crates/buildah-ffi/shim/go.mod after go mod tidy on Linux:
go.podman.io/storagev1.64.1go.podman.io/image/v5v5.41.2go.podman.io/commonv0.69.2
Older github.com/containers/buildah 1.43.x releases are affected by GO-2026-5116 (build breakout via a malicious Containerfile or Git HTTP server). This tree tracks v1.45.1, not that line.
go.sum is the lock. If go mod tidy moves a transitive pin, update this list to match.
Limitations
- Real builds are Linux-only. The macOS and other non-Linux binaries link the stub.
oci-buildercallsstartupbefore clap, so on those hosts--helpexits 8 with a message that Buildah is available on Linux only. RUNunderociorrootlessisolation needs runc or crun onPATH. They are not linked in.- Signing is not implemented. Verification uses the pure-Go OpenPGP tag.
- One store per process.
- The Go runtime takes signals such as
SIGURG. Cancel throughCancelTokeninstead of relying on a Rustctrl-chook inside a build. - Rootless re-exec changes
argv[0]and the original process does not continue after the child exits. - When the
seccomptag is enabled,libseccompis a dynamic runtime dependency. cargo testof the library does not drive the engine. The test harness is already multithreaded, so a Buildah re-exec from inside it would restart the harness.crates/oci-builder/tests/build_image.rsruns theoci-builderbinary. It buildsFROM scratchplusCOPYwith thevfsdriver andchrootisolation, and it pushes only whenROB_TEST_REGISTRYis set (HTTP, TLS verification skipped).
Release
Pushing a tag vX.Y.Z runs .github/workflows/release.yml. The workflow runs the CI tests, then publishes buildah-ffi and oci-builder with crates.io trusted publishing. The tag must match workspace.package.version.
For each crate, the GitHub trusted publisher is workflow filename release.yml and environment release. Trusted publishing updates a crate that already exists, so the first upload of a crate still uses an API token. Later versions do not store a crates.io token in this repository.
License
Apache-2.0, the same license as Buildah. See LICENSE.