Buildah can build OCI images without needing a Docker daemon running, making it a more flexible and potentially more secure option for CI/CD pipelines.
Let’s see this in action with a simple Dockerfile and a buildah command.
# Dockerfile
FROM alpine:latest
RUN echo "Hello from my OCI image!" > /app/hello.txt
CMD ["cat", "/app/hello.txt"]
Now, to build this without Docker:
# Create a temporary directory for the build context
mkdir my-app-context
cp Dockerfile my-app-context/
# Build the image using buildah
buildah bud --format docker -t my-oci-image:latest ./my-app-context
# Verify the image was built (it should appear in your local image list)
buildah images | grep my-oci-image
# Run a container from the image
buildah run my-oci-image:latest -- cat /app/hello.txt
The output of the buildah run command will be:
Hello from my OCI image!
This demonstrates that buildah successfully built and ran an OCI-compatible image, all without a docker daemon.
The fundamental problem buildah addresses is the reliance on a privileged, long-running daemon process that Docker requires. This daemon has broad system access and can be a security concern, especially in shared or untrusted environments. Furthermore, managing multiple Docker daemons or integrating Docker into environments where it’s not natively available (like some Kubernetes setups or serverless functions) can be cumbersome. buildah works by directly interacting with the OCI image specification. It doesn’t need to run a daemon; instead, it creates containers as isolated working environments, modifies them by applying layers from your Dockerfile or other instructions, and then commits the final state as an OCI image. This means each build is a self-contained operation.
Internally, buildah uses runC (or another OCI runtime) to create a container based on a base image. It then mounts this container’s filesystem, allowing you to execute commands within it (like your RUN instructions). Once all instructions are executed, buildah commits the container’s filesystem as a new image layer, creating a new OCI image. This process can be repeated for each instruction in a Dockerfile or for a sequence of buildah commands. The key levers you control are the base image, the commands you execute within the container, and how you package the final output (e.g., as a tarball or directly into your local image store).
When you use buildah bud --format docker, buildah interprets your Dockerfile and produces an image that is compatible with the Docker image format. This is crucial for interoperability, as many tools and registries expect images in this format. buildah doesn’t just blindly execute Docker commands; it translates the Dockerfile instructions into its own internal operations, leveraging OCI primitives. This means it can build images from Containerfiles (the upstream name for Dockerfiles) or even from scratch, specifying image layers and metadata directly.
A common point of confusion is how buildah handles intermediate containers and the final image. Unlike Docker, which creates and discards intermediate containers as part of a single docker build command, buildah can be used to explicitly create, manage, and commit containers at any stage. You can even run buildah commands to mount a container’s filesystem, make manual changes, and then commit it. This granular control allows for more complex build processes or debugging by inspecting the state of a container mid-build. The buildah bud command is essentially a convenience wrapper that orchestrates this process for Dockerfiles.
The next step in managing container images without a daemon is exploring Podman, which uses buildah under the hood for image building and provides a daemonless container runtime experience.