Chapter 6. Building container images with Buildah

The buildah command lets you create container images from a working container, a Dockerfile, or from scratch. The resulting images are OCI compliant, so they will work on any container runtime that meets the OCI Runtime Specification (such as Docker and CRI-O).

This section describes how to use the buildah command to create and otherwise work with containers and container images.

6.1. Understanding Buildah

Using Buildah is different from building images with the docker command in the following ways:

  • No Daemon!: Bypasses the Docker daemon! So no container runtime (Docker, CRI-O, or other) is needed to use Buildah.
  • Base image or scratch: Lets you not only build an image based on another container, but also lets you start with an empty image (scratch).
  • Build tools external: Doesn’t include build tools within the image itself. As a result, Buildah:

    • Reduces the size of images you build
    • Makes the image more secure by not having the software used to build the container (like gcc, make, and dnf) within the resulting image.
    • Creates images that require fewer resources to transport the images (because they are smaller).

Buildah is able to operate without Docker or other container runtimes by storing data separately and by including features that let you not only build images, but run those images as containers as well. By default, Buildah stores images in an area identified as containers-storage (/var/lib/containers).

Note

The containers-storage location that the buildah command uses by default is the same place that the CRI-O container engine uses for storing local copies of images. So images pulled from a registry by either CRI-O or Buildah, or committed by the buildah command, will be stored in the same directory structure. Currently, however, CRI-O and Buildah cannot share containers, though they can share images.

There are more than a dozen options to use with the buildah command. Some of the main activities you can do with the buildah command include:

  • Build a container from a Dockerfile: Use a Dockerfile to build a new container image (buildah bud).
  • Build a container from another image or scratch: Build a new container, starting with an existing base image (buildah from <imagename>) or from scratch (buildah from scratch)
  • Inspecting a container or image: View metadata associated with the container or image (buildah inspect)
  • Mount a container: Mount a container’s root filesystem to add or change content (buildah mount).
  • Create a new container layer: Use the updated contents of a container’s root filesystem as a filesystem layer to commit content to a new image (buildah commit).
  • Unmount a container: Unmount a mounted container (buildah umount).
  • Delete a container or an image: Remove a container (buildah rm) or a container image (buildah rmi).

For more details on Buildah, see the GitHub Buildah page. The GitHub Buildah site includes man pages and software that might be more recent than is available with the RHEL version. Here are some other articles on Buildah that might interest you:

6.1.1. Installing Buildah

The buildah package is available with the container-tools module in RHEL 8 (yum module install container-tools). You can install the buildah package separately by typing:

# yum -y install buildah

With the buildah package installed, you can refer to the man pages included with the buildah package for details on how to use it. To see the available man pages and other documentation, then open a man page, type:

# rpm -qd buildah
# man buildah
buildah(1)         General Commands Manual         buildah(1)

NAME
 Buildah - A command line tool that facilitates building OCI container images.
...

The following sections describe how to use buildah to get containers, build a container from a Dockerfile, build one from scratch, and manage containers in various ways.

6.2. Getting images with Buildah

To get a container image to use with buildah, use the buildah from command. Here’s how to get a RHEL 8 image from the Red Hat Registry as a working container to use with the buildah command:

# buildah from registry.redhat.io/ubi8/ubi
Getting image source signatures
Copying blob…
Writing manifest to image destination
Storing signatures
ubi-working-container
# buildah images
IMAGE ID      IMAGE NAME                          CREATED AT         SIZE
3da40a1670b5  registry.redhat.io/ubi8/ubi:latest  May 8, 2019 21:55  214 MB
# buildah containers
CONTAINER ID  BUILDER  IMAGE ID     IMAGE NAME         CONTAINER NAME
c6c9279ecc0f     *     3da40a1670b5 ...ubi8/ubi:latest ubi-working-container

Notice that the result of the buildah from command is an image (registry.redhat.io/ubi8/ubi:latest) and a working container that is ready to run from that image (ubi-working-container). Here’s an example of how to execute a command from that container:

# podman run ubi-working-container cat /etc/redhat-release
Red Hat Enterprise Linux release 8.0

The image and container are now ready for use with Buildah.

6.3. Building an image from a Dockerfile with Buildah

With the buildah command, you can create a new image from a Dockerfile. The following steps show how to build an image that includes a simple script that is executed when the image is run.

This simple example starts with two files in the current directory: Dockerfile (which holds the instructions for building the container image) and myecho (a script that echoes a few words to the screen):

# ls
Dockerfile  myecho
# cat Dockerfile
FROM registry.redhat.io/ubi8/ubi
ADD myecho /usr/local/bin
ENTRYPOINT "/usr/local/bin/myecho"
# cat myecho
echo "This container works!"
# chmod 755 myecho
# ./myecho
This container works!

With the Dockerfile in the current directory, build the new container as follows:

# buildah bud -t myecho .
STEP 1: FROM registry.redhat.io/ubi8/ubi
STEP 2: ADD myecho /usr/local/bin
STEP 3: ENTRYPOINT "/usr/local/bin/myecho"

The buildah bud command creates a new image named myecho. To run see that new image, type:

# buildah images
IMAGE NAME        IMAGE TAG  IMAGE ID      CREATED AT          SIZE
localhost/myecho  latest     a3882af49784  Jun 21, 2019 12:21  216 MB

Next, you can run the image, to make sure it is working.

6.3.1. Running the image you built

To check that the image you built previously works, you can run the image using podman run:

# podman run myecho
This container works!

6.3.2. Inspecting a container with Buildah

With buildah inspect, you can show information about a container or image. For example, to inspect the myecho image you created earlier, type:

# buildah inspect myecho | less
{
 "Type": "buildah 0.0.1",
 "FromImage": "docker.io/library/myecho:latest",
 "FromImage-ID": "e2b190ac8...",
 "Config": "{\"created\":\"2018-11-13...

 "Entrypoint": [
      "/usr/local/bin/myecho"
   ],
   "WorkingDir": "/",
   "Labels": {
      "architecture": "x86_64",
      "authoritative-source-url": "registry.access.redhat.com",
      "build-date": "2018-09-19T20:46:28.459833",

To inspect a container from that same image, type the following:

# buildah inspect myecho-working-container | less
{
    "Type": "buildah 0.0.1",
    "FromImage": "docker.io/library/myecho:latest",
    "FromImage-ID": "e2b190a...",
    "Config": "{\"created\":\"2018-11-13T19:5...
...
    "Container": "myecho-working-container",
    "ContainerID": "c0cd2e494d...",
    "MountPoint": "",
    "ProcessLabel": "system_u:system_r:svirt_lxc_net_t:s0:c89,c921",
    "MountLabel": "",

Note that the container output has added information, such as the container name, container id, process label, and mount label to what was in the image.

6.4. Modifying a container to create a new image with Buildah

There are several ways you can modify an existing container with the buildah command and commit those changes to a new container image:

  • Mount a container and copy files to it
  • Use buildah copy and buildah config to modify a container

Once you have modified the container, use buildah commit to commit the changes to a new image.

6.4.1. Using buildah mount to modify a container

After getting an image with buildah from, you can use that image as the basis for a new image. The following text shows how to create a new image by mounting a working container, adding files to that container, then committing the changes to a new image.

Type the following to view the working container you used earlier:

# buildah containers
CONTAINER ID BUILDER IMAGE ID     IMAGE NAME  CONTAINER NAME

dc8f21af4a47   *     1456eedf8101 registry.redhat.io/ubi8/ubi:latest
               ubi-working-container
6d1ffccb557d   *     ab230ac5aba3 docker.io/library/myecho:latest
               myecho-working-container

Mount the container image and set the mount point to a variable ($mymount) to make it easier to deal with:

# mymount=$(buildah mount myecho-working-container)
# echo $mymount
/var/lib/containers/storage/devicemapper/mnt/176c273fe28c23e5319805a2c48559305a57a706cc7ae7bec7da4cd79edd3c02/rootfs

Add content to the script created earlier in the mounted container:

# echo 'echo "We even modified it."' >> $mymount/usr/local/bin/myecho

To commit the content you added to create a new image (named myecho), type the following:

# buildah commit myecho-working-container containers-storage:myecho2

To check that the new image includes your changes, create a working container and run it:

# buildah images
IMAGE ID     IMAGE NAME     CREATED AT          SIZE
a7e06d3cd0e2 docker.io/library/myecho2:latest
                            Oct 12, 2017 15:15  3.144 KB
# buildah from docker.io/library/myecho2:latest
myecho2-working-container
# buildah run myecho2-working-container
This container works!
We even modified it.

You can see that the new echo command added to the script displays the additional text.

When you are done, you can unmount the container:

# buildah umount myecho-working-container

6.4.2. Using buildah copy and buildah config to modify a container

With buildah copy, you can copy files to a container without mounting it first. Here’s an example, using the myecho-working-container created (and unmounted) in the previous section, to copy a new script to the container and change the container’s configuration to run that script by default.

Create a script called newecho and make it executable:

# cat newecho
echo "I changed this container"
# chmod 755 newecho

Create a new working container:

# buildah from myecho:latest
myecho-working-container-2

Copy newecho to /usr/local/bin inside the container:

# buildah copy myecho-working-container-2 newecho /usr/local/bin

Change the configuration to use the newecho script as the new entrypoint:

# buildah config --entrypoint "/bin/sh -c /usr/local/bin/newecho "myecho-working-container-2

Run the new container, which should result in the newecho command being executed:

# buildah run myecho-working-container-2
I changed this container

If the container behaved as you expected it would, you could then commit it to a new image (mynewecho):

# buildah commit myecho-working-container-2 containers-storage:mynewecho

6.5. Creating images from scratch with Buildah

Instead of starting with a base image, you can create a new container that holds no content and only a small amount of container metadata. This is referred to as a scratch container. Here are a few issues to consider when choosing to create an image starting from a scratch container with the buildah command:

  • With a scratch container, you can simply copy executables that have no dependencies to the scratch image and make a few configuration settings to get a minimal container to work.
  • To use tools like yum or rpm packages to populate the scratch container, you need to at least initialize an RPM database in the container and add a release package. The example below shows how to do that.
  • If you end up adding a lot of RPM packages, consider using the rhel or rhel-minimal base images instead of a scratch image. Those base images have had documentation, language packs, and other components trimmed out, which can ultimately result in your image being smaller.

This example adds a Web service (httpd) to a container and configures it to run. In the example, instead of committing the image to Buildah (containers-storage which stores locally in /var/lib/containers), we illustrate how to commit the image so it can be managed by the local Docker service (docker-daemon which stores locally in /var/lib/docker). You could just have easily committed it to Buildah, which would let you then push it to a Docker service (docker), a local OSTree repository (ostree), or other OCI-compliant storage (oci). (Type man buildah push for details.)

To begin, create a scratch container:

# buildah from scratch
working-container

This creates just an empty container (no image) that you can mount as follows:

# scratchmnt=$(buildah mount working-container)
# echo $scratchmnt
/var/lib/containers/storage/devicemapper/mnt/cc92011e9a2b077d03a97c0809f1f3e7fef0f29bdc6ab5e86b85430ec77b2bf6/rootfs

Initialize an RPM database within the scratch image and add the redhat-release package (which includes other files needed for RPMs to work):

# dnf install -y --releasever=8 --installroot=$scratchmnt redhat-release

Install the httpd service to the scratch directory:

# dnf install -y --setopt=reposdir=/etc/yum.repos.d \
     --installroot=$scratchmnt \
     --setopt=cachedir=/var/cache/dnf httpd

Add some text to an index.html file in the container, so you will be able to test it later:

# echo "Your httpd container from scratch worked." > $scratchmnt/var/www/html/index.html

Instead of running httpd as an init service, set a few buildah config options to run the httpd daemon directly from the container:

# buildah config --cmd "/usr/sbin/httpd -DFOREGROUND" working-container
# buildah config --port 80/tcp working-container
# buildah commit working-container docker-daemon:myhttpd:latest

By default, the buildah commit command adds the docker.io repository name to the image name and copies the image to the storage area for your local Docker service (/var/lib/docker). For now, you can use the Image ID to run the new image as a container with the docker command:

# docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
docker.io/myhttpd   latest              47c0795d7b0e        9 minutes ago       665.6 MB
# docker run -p 8080:80 -d --name httpd-server 47c0795d7b0e
# curl localhost:8080
Your httpd container from scratch worked.

6.6. Removing images or containers with Buildah

When you are done with particular containers or images, you can remove them with buildah rm or buildah rmi, respectively. Here are some examples.

To remove the container created in the previous section, you could type the following to see the mounted container, unmount it and remove it:

# buildah containers
CONTAINER ID  BUILDER  IMAGE ID     IMAGE NAME                       CONTAINER NAME
05387e29ab93     *     c37e14066ac7 docker.io/library/myecho:latest  myecho-working-container
# buildah mount
05387e29ab93 /var/lib/containers/storage/devicemapper/mnt/9274181773a.../rootfs
# buildah umount 05387e29ab93
# buildah rm 05387e29ab93
05387e29ab93151cf52e9c85c573f3e8ab64af1592b1ff9315db8a10a77d7c22

To remove the image you created previously, you could type the following:

# buildah rmi docker.io/library/myecho:latest
untagged: docker.io/library/myecho:latest
ab230ac5aba3b5a0a7c3d2c5e0793280c1a1b4d2457a75a01b70a4b7a9ed415a

6.7. Using container registries with Buildah

With Buildah, you can push and pull container images between your local system and public or private container registries. The following examples show how to:

  • Push containers to and pull them from a private registry with buildah.
  • Push and pull container between your local system and the Docker Registry.
  • Use credentials to associate your containers with a registry account when you push them.

Use the skopeo command, in tandem with the buildah command, to query registries for information about container images.

6.7.1. Pushing containers to a private registry

Pushing containers to a private container registry with the buildah command works much the same as pushing containers with the docker command. You need to:

  • Set up a private registry (OpenShift provides a container registry or you can set up a Red Hat Quay container registry).
  • Create or acquire the container image you want to push.
  • Use buildah push to push the image to the registry.

To push an image from your local Buildah container storage, check the image name, then push it using the buildah push command. Remember to identify both the local image name and a new name that includes the location. For example, a registry running on the local system that is listening on TCP port 5000 would be identified as localhost:5000.

# buildah images
IMAGE ID     IMAGE NAME                       CREATED AT          SIZE
cb702d492ee9 docker.io/library/myecho2:latest Nov 12, 2018 16:50     3.143 KB

# buildah push --tls-verify=false myecho2:latest localhost:5000/myecho2:latest
Getting image source signatures
Copying blob sha256:e4efd0...
...
Writing manifest to image destination
Storing signatures

Use the curl command to list the images in the registry and skopeo to inspect metadata about the image:

# curl http://localhost:5000/v2/_catalog
{"repositories":["myatomic","myecho2"]}
# curl http://localhost:5000/v2/myecho2/tags/list
{"name":"myecho2","tags":["latest"]}
# skopeo inspect --tls-verify=false docker://localhost:5000/myecho2:latest | less
{
    "Name": "localhost:5000/myecho2",
    "Digest": "sha256:8999ff6050...",
    "RepoTags": [
        "latest"
    ],
    "Created": "2017-11-21T16:50:25.830343Z",
    "DockerVersion": "",
    "Labels": {
        "architecture": "x86_64",
        "authoritative-source-url": "registry.redhat.io",

At this point, any tool that can pull container images from a container registry can get a copy of your pushed image. For example, on a RHEL 7 system you could start the docker daemon and try to pull the image so it can be used by the docker command as follows:

# systemctl start docker
# docker pull localhost:5000/myecho2
# docker run localhost:5000/myecho2
This container works!

6.7.2. Pushing containers to the Docker Hub

You can use your Docker Hub credentials to push and pull images from the Docker Hub with the buildah command. For this example, replace the username and password (testaccountXX:My00P@sswd) with your own Docker Hub credentials:

# buildah push --creds testaccountXX:My00P@sswd \
     docker.io/library/myecho2:latest docker://testaccountXX/myecho2:latest

As with the private registry, you can then get and run the container from the Docker Hub with the podman, buildah or docker command:

# podman run docker.io/textaccountXX/myecho2:latest
This container works!
# buildah from docker.io/textaccountXX/myecho2:latest
myecho2-working-container-2
# podman run myecho2-working-container-2
This container works!