What is Buildah

I've been following the Buildah posts on projectatomic.com for some time now, but I never got to try it out in detail. Earlier this week, Red Hat announced the 1.0 release of Buildah, the perfect reason to try it out for real.

In short, Buildah is a tool to build container images. Why a new container image builder if we have docker build and others already?

To start, Buildah doesn't need a daemon running all the time, which makes it very lightweight in terms of resources. This makes it very suitable for pipeline-use. The build server doesn't need to have Docker installed to build new container images.

The container images are not built by Docker, but they can be used with Docker, just as a normal Docker image. Buildah builds OCI-compatible images. This makes them usable by any runtime that can handle OCI-images.

A container image can be built with a simple bash script. Yes, bash scripting again, but at least you can use normal bash skills, without having to handle with the Dockerfile format.

If you don't want to rewrite all of your Dockerfiles, but you want to get rid of that big fat Docker daemon, there is good news. Buildah can build container images from any Dockerfile, which is good if you want a slow transition to Buildah over time.

Buildah from scratch

In the past, I've created a simple Docker image to use for building rpm packages. The following Dockerfile is used:

FROM centos:centos7

MAINTAINER Thomas Cassaert "[email protected]"

RUN yum update -y \
  && yum -y install ruby-devel rubygems gcc make rpmdevtools \
  && yum clean all \
  && rm -rf /var/cache/yum

RUN gem install --no-ri --no-rdoc fpm

WORKDIR /src

CMD "/src/fpm.sh"

This image expects a bash script called fpm.sh in the src directory in the container.

I start this container with

docker run -v "$(pwd)":/src tcassaert/fpm-builder

The fpm.sh script needs to be available in pwd and can be as simple as

#!/bin/bash

set -ex

fpm -s dir -t rpm -n demo.rpm .

Now I want to rebuild this image with Buildah.

To get started with Buildah, it needs to be installed of course. On CentOS, it's as easy as

yum install buildah

The easiest way to get started is by building a bash script step by step.

#!/bin/bash

set -ex

ctr=$(buildah from scratch)
ctrmnt=$(buildah mount ${ctr})

This contains only two real Buildah steps, but there is already happening a lot. In the first instruction, we ask Buildah to start from scratch. This really is an empty container, with just some metadata.

In the second instruction, we ask Buildah to mount that empty container. That mountpoint should be located at /var/lib/containers/storage/overlay.

[root@buildah ~]# echo $ctrmnt
/var/lib/containers/storage/overlay/be44d91ce2ab9971ff75f1b0c22e8bf7b3e727b44cdf23b7571cee26079d1d45/merged

Let's add a new command to our script:

yum install -y --installroot ${ctrmnt} bash coreutils gcc ruby-devel make rpm-build --releasever 7 --setopt=tsflags=nodocs --setopt=override_install_langs=en_US.utf8

This step installs the minimum requirements into the $ctrmount. Note that this command is run from the Buildah host, as there is no yum in the image. The extra command like --setopt=tsflags=nodocs --setopt=override_install_langs=en_US.utf8 are inspired by pixdrift his buildah scripts and on Buildah GitHub issue #532. They are used to make the image as small as possible.

The function of --releasever is

[root@buildah ~]# yum help | grep releasever
  --releasever=RELEASEVER
                        set value of $releasever in yum config and repo files

The next step should be to clean the yum cache.

if [ -d "${ctrmnt}" ]; then
  rm -rf "${ctrmnt}"/var/cache/yum
fi

The Docker image is using the /src directory as workingdir, so it needs to be available in the new image as well. This is easy.

mkdir -p "${ctrmnt}"/src

Now it's time to install fpm in the image. Just as with the installation of rpm packages, it is possible to use the Buildah host's gem install. The downside is that you need to have the packages installed in the Buildah host to do this.

gem install -i "${ctrmnt}"/usr/share/gems/ --bindir "${ctrmnt}"/usr/bin --no-ri --no-rdoc fpm

We should have everything installed in the image right now. The only thing left is some configuration of the image.

buildah config --label name=fpm-buildah ${ctr}
buildah config --workingdir /src ${ctr}
buildah config --cmd /src/fpm.sh ${ctr}

In these instructions we set the name of the image, the workingdir and the command the container should execute.

Everything is ready to be unmounted and to be committed.

buildah unmount ${ctr}
buildah commit ${ctr} tcassaert/buildah-fpm

The resulting script looks like this.

#!/bin/bash

set -ex

# start new container from scratch
ctr=$(buildah from scratch)
ctrmnt=$(buildah mount ${ctr})

# install the packages
yum install --installroot ${ctrmnt} bash coreutils gcc ruby-devel make rpm-build --releasever 7 --setopt=tsflags=nodocs --setopt=override_install_langs=en_US.utf8 -y

# clean up yum cache
if [ -d "${ctrmnt}" ]; then
  rm -rf "${ctrmnt}"/var/cache/yum
fi

# make the /src directory
mkdir -p "${ctrmnt}"/src

# install fpm
gem install -i "${ctrmnt}"/usr/share/gems/ --bindir "${ctrmnt}"/usr/bin --no-ri --no-rdoc fpm

# configure container label and command
buildah config --label name=buildah-fpm ${ctr}
buildah config --workingdir /src $ctr
buildah config --cmd /src/fpm.sh ${ctr}

# commit the image
buildah unmount ${ctr}
buildah commit ${ctr} tcassaert/fpm-buildah

Executing this script builds the requested image. I've time the time it took to build this immage.

real    3m2.321s
user    1m19.610s
sys     0m25.120s

Now, let's build the same image using Docker and see how long it takes.

real    2m15.953s
user    0m0.093s
sys     0m0.094s

Buildah appears to be slower, which is not unnormal, as it builds everything from scratch.

Full Dockerless

Next to Buildah, RedHat is also building Podman. It uses the same #nobigfatdaemons philosophy as Buildah, promoted by @rhatdan. Podman is a CLI tool to run OCI-compatible containers using pods. Podman uses by default runc to run the containers. Most of the Docker cli commands are the same in Podman.

To make this blog Dockerless, we'll use Podman to test the image.

Installing Podman is as easy as installing Buildah.

yum install -y podman

You can simply list the current available images with podman images.

[root@buildah ~]# podman images
docker.io/tcassaert/fpm-buildah   latest   0f80890a7406   About a minute ago   328MB

We can see the image we built earlier just as we would if we pulled it from the Docker Store. The image is 328MB in size. Let's compare it with the image built from the Dockerfile.

[root@buildah ~]# podman pull tcassaert/fpm-builder
[root@buildah ~]# podman images
docker.io/tcassaert/fpm-builder   latest   b1ffce8113cf   2 weeks ago          357MB
docker.io/tcassaert/fpm-buildah   latest   0f80890a7406   About a minute ago   328MB

The Buildah image is a little bit smaller, 29MB to be exact.

The only thing left now is to test the image...

[root@buildah ~]# podman run -v $(pwd):/src fpm-buildah
{:timestamp=>"2018-06-08T09:56:12.062668+0000", :message=>"Created package", :path=>"demo.rpm-1.0-1.x86_64.rpm"}

Conclusion

Buildah is totally ready to make the #nobigfatdaemons a thing. The images are smaller, easy to build and work with any OCI-compatible runtime and most importantly, the don't require a daemon to be running. So even when using Kubernetes with CRI-O, you don't need to adjust anything at all to use the images built by Buildah.

With a template script, it's just about filling in the needed packages and the container name.

Podman is a nice CLI-tool to replace the need for Docker. The transition to Podman from Docker is just changing every docker instruction to podman.