Buildah: Granularity & Control

The goal of this lab is to introduce you to Buildah and the flexibility it provides when you need to build container images your way. There are a lot of different use cases that just "feel natural" when building container images, but you often, you can't quite wire together and elegant solutions with the client server model of existing container engines. In comes Buildah. To get started, lets introduce some basic decisions you need to think through when building a new container image.
  • Image vs. Scratch: Do you want to start with an existing container image as the source for your new container image, or would you prefer to build completely from scratch? Source images are the most common route, but it can be nice to build from scratch if you have small, statically linked binaries.
  • Inside vs. Outside: Do you want to execute the commands to build the next container image layer inside the container, or would you prefer to use the tools on the host to build the image? This is completely new concept with Buildah, but with existing container engines, you always build from within the container. Building outside the container image can be useful when you want to build a smaller container image, or an image that will always be ran read only, and never built upon. Things like Java would normally be built in the container because they typically need a JVM running, but installing RPMs might happen from outside because you don't want the RPM database in the container.
  • External vs. Internal Data: Do you have everything you need to build the image from within the image? Or, do you need to access cached data outside of the build process? For example, It might be convenient to mount a large cached RPM cache inside the container during build, but you would never want to carry that around in the production image. The use cases for build time mounts range from SSH keys to Java build artifacts.
Alright, let's walk through some common scenarios with Buildah.

Preparation Work

Just like Podman, Buildah can execute in rootless mode, but since you have tools on the container host interacting files in the container image, you need to make Buildah think it's running as root. Buildah comes with a sub-command called unshare which does just this. It puts our shell into a user namespace just like when you have a root shell in a container. The difference is, this shell has access to tools installed on the container host, instead of in the container image. Before we complete the rest of this lab, execute the "buildah unshare" command. Think of this as making yourself root, without actually making yourself root:

buildah unshare

Now, look at who your shell thinks you are:

It's looks like you are root, but you really aren't, but let's prove it:
touch /etc/shadow
The touch command fails because you're not actually root. Really, the touch command executed as an arbitrary user ID in your /etc/subuid range.

Basic Build

First declare what image you want to start with as a source. In this case, we will start with Red Hat Universal Base Image:
buildah from ubi8
This will create a "reference" to what Buildah calls a "working container" - think of them as a starting point to attach mounts and commands. Check it out here:
buildah containers
Now, we can mount the image source. In effect, this will trigger the graph driver to do its magic, pull the image layers together, add a working copy-on-write layer, and mount it so that we can access it just like any directory on the system:
buildah mount ubi8-working-container
Now, lets add a single file to the new container image layer. The Buildah mount command can be ran again to get access to the right directory:
echo "hello world" > $(buildah mount ubi8-working-container)/etc/hello.conf
Lets analyze what we just did. It's super simple, but a different mentality if you come from using other container engines. First, list the directory in the copy-on-write layer:
ls -alh $(buildah mount ubi8-working-container)/etc/
You should see hello.conf right there. Now, cat the file:
cat $(buildah mount ubi8-working-container)/etc/hello.conf
You should see the text you expect. Now, lets commit this copy-on-write layer as a new image layer:
buildah commit ubi8-working-container ubi8-hello

Now, we can see the new image layer in our local cache. We can view it with Buildah:

buildah images

Or Podman, since they both use the same image store:

podman images

When we are done, we can clean up our environment quite nicely. The following command will delete references to "working containers" and completely remove their mounts:

buildah delete -a


Exit the user namespace:

You can verify that you've successfully exited the namespace by running whoami, which should no longer display root.


Now, you have a pretty good understanding of the cases where Buildah really shines. You can start from scratch, or use an existing image, use tools installed on the container host (not in the container image), and move data around as needed. This is a very flexible tool that should fit quite nicely in your tool belt. Buildah lets you script builds with any language you want, and build tiny images with only the bare minimum of utilities needed inside the image.