Buildah - Build OCI Container Images

Linux
Containers
Published

July 29, 2020

Modified

February 24, 2023

buildah

…user-space tool to interactively build container images…

  • …replicates commands that are found in a Dockerfile
  • …enables a build workflow as non-root user
  • …compatible to OCI container images with Docker format
  • …supports multi-stage customizable image layer caching
  • …use the same image and storage components as CRI-OCI
  • References…

Configuration…

  • …uses runc to execute containers
  • Directory /etc/containers/
    • registries.conf container image name completion
    • mounts.conf paths automatically mounted inside containers

images, containers & from

# list all local images
buildah images

Build a working container from an existing image…

# list local working containers
buildah containers
  • …defaults to appending -working-container to the image’s name
  • CLI conveniently returns the name of the new container…
  • …assigning the returned value to a shell variable
  • from pulls and creates a working container in one step…
# ...for example latest RockyLinux
>>> buildah from rockylinux/rockylinux:8
...
rockylinux-working-container     # <- name of the container

# ...automatically started
>>> buildah containers     
CONTAINER ID  BUILDER  IMAGE ID     IMAGE NAME                       CONTAINER NAME
c5162c15716e     *     c830f8e8f82b docker.io/library/rockylinux:... rockylinux-working-container

# shell into the container
>>> buildah run rockylinux-working-container bash
[root@c5162c15716e /]# exit
exit

# remove the container
>>> buildah rm rockylinux-working-container      
c5162c15716e2f679a23ca59347ca380bbee238b8759e02ebd8927ef2aae417b

Dockerfile vs Scripting

The build sub-command takes a Dockerfile [^2] as input and produces container image…

FROM rockylinux:latest
MAINTAINER Victor Penso
RUN set -x \
    && dnf -y update; dnf -y clean all \
    && dnf install -y epel-release dnf-plugins-core \
    && dnf config-manager --set-enabled powertools \
    && dnf install -y slurm-slurmctld
EXPOSE 6817
CMD ["/usr/sbin/slurmctld", "-Dvvvv"]
  • …option -f specify the path to the Dockerfile
  • …option -t to tag the container image
buildah build -f /tmp/Dockerfile -t rocky-slurmctld
# list the container image
podman images rocky-slurmctld
# create a container from the image...
podman run --rm rocky-slurmctld

Same as above with buildah sub-commands…

container=rocky-slurm
buildah from --name $container rockylinux
buildah run $container -- dnf -y update; dnf -y clean all
buildah run $container -- dnf install -y epel-release dnf-plugins-core
buildah run $container -- dnf config-manager --set-enabled powertools
buildah run $container -- dnf install -y slurm-slurmctld
buildah config --port 6817 $container
buildah config --cmd '/usr/sbin/slurmctld -Dvvvv' $container
buildah commit $container

buildah commands can be used with any scripting language to automate container build completely in user-space (no additional access privileges required).

run & copy

run executes a command in the container…

  • …run commands as part of the build process
    • …intended for debugging
    • …use podman to run containers in production
# start an interactive shell...
buildah run --terminal $container -- $SHELL

# execute a single command...
buildah run $container -- dnf install -y git
buildah run $container --workingdir /tmp -- ...

# run multiple commands...
buildah run $container -- /bin/bash -ic '
        dnf -y update
        dnf -y clean all
'

copy files to a container without mounting it

# dummy executable
echo 'echo Hello World' > /tmp/hello_world && chmod +x /tmp/hello_world

# create a new container and copy a the executable
container=$(buildah from alpine)
buildah copy $container /tmp/hello_world /usr/local/bin

# use the executable as entrypoint
buildah config --entrypoint '/bin/sh -c /usr/local/bin/hello_world' $container
buildah commit $container localhost/hello_world:latest
podman run -it localhost/hello_world

config

config modifies the container meta-configuration…

Example --entrypoint configuration:

# configure the container entrypoint...
container=$(buildah from rockylinux)
buildah run $container -- dnf install -y httpd

# configure the process to run on container start
buildah config --entrypoint "/usr/sbin/httpd -DFOREGROUND" $container

Example --port and --cmd configuration:

container=$(buildah from rockylinux)
buildah run $container -- dnf install -y nginx
buildah run $container -- systemctl enable nginx.service

# enable port mapping
buildah config --port 8080:80 $container

# configure the command to execute on container start
buildah config --cmd /usr/sbin/init $container

unshare, mount & umount

Mount a working container’s root file-system…

# ...running in rootless mode...create the mount namespace
>>> buildah unshare     
# ...access the container root file-system
>>> buildah mount fedora-working-container
/home/vpenso/.local/share/containers/storage/overlay/fcbf352899f44665d64b56c30799027c6f9c1dfe3a4b5f4a901ed7ebc240b954/merged
# ...work with the mount
# ...remove the mount
>>> buildah umount fedora-working-container                                                                                        
bec7cb118fe9f1825226387faf7af8e34cbac2fc1c7a7e238fa8fe644f2abcd2

unshare runs a command inside of a modified user namespace…

  • …no command…launch a shell in user namespace
  • …user ID mapped to root (UID=0)
  • …user home-directory owned by root
  • /usr owned by nfsnobody (or nobody).
cat buildah-script.sh << _EOF
#!/bin/sh
ctr=$(buildah from scratch)
mnt=$(buildah mount $ctr)
dnf -y install --installroot=$mnt PACKAGES
dnf -y clean all --installroot=$mnt
buildah config --entrypoint="/bin/PACKAGE" --env "FOO=BAR" $ctr
buildah commit $ctr imagename
buildah unmount $ctr
_EOF
buildah unshare buildah-script.sh

commit

Create a container image from a working container…

                     ┌────────┬──────────────── container registry on localhost
                     │        │
                     │        │      ┌────┬──── optional tag or version information

buildah commit hello localhost/hello:2.12.1

               │   │           └───┴─────────── name of the container image
               │   │
               └───┴─────────────────────────── name of working container

Noteworthy options…

  • --rm …remove working container afterwards
  • --format …for the image manifest and configuration data
    • docker version 2, using schema format 2 for the manifest
    • oci…default…OCI image-spec v1.0

push

…image from local storage to a specified destination…

  • …decompressing and recompessing layers as needed
  • …read man 5 containers-transports
  • Destinations….
    • dir:…existing local directory
    • docker://…docker compatible registry
    • oci:… OCI container layout
    • oci-archive:…OCI container image archive
>>> buildah push fedora dir:fedora/
>>> ls -1 fedora   
  2ecb6df959942dd2fdeb65606ca2e42a54f8c06af10eeb594fdfc3e2656c53d1
  62946078034b7fe37984579d9b82ccf20cc98ffcd6517cf79ffad18e06fe2b23
  manifest.json
  version

OCI Image Layout

>>> buildah push fedora oci:fedora/
>>> ls -1 fedora 
blobs/
index.json
oci-layout

# ...build an OCI image archive
>>> buildah push localhost/hello:2.12.1 oci-archive:/tmp/hello.tar
>>> tar -tf /tmp/hello.tar
blobs/
blobs/sha256/
blobs/sha256/3bb03adbd305b52d94c0db084885c730f35791e5b948cc4aca084a9c9daa95d4
blobs/sha256/9dad25583c4d8ccfefa2758f6ade83a162e92de4e810aa683a38a7bac3c77bc1
blobs/sha256/cb42c62e4863a105c291cb68c2caa9ce838e48fcfb3f92ec8c17e70e1ec3c438
blobs/sha256/f54fcbf88ba2e4838e115116f98b438e0019e2c1eb9703ac79f10203e32dcdb0
index.json
oci-layout

Examples

Build the hello source code within the container…

container=$(buildah from fedora:37)

buildah config --label maintainer="John Snow <j.snow@example.com>" $container

wget https://ftp.gnu.org/gnu/hello/hello-2.12.1.tar.gz
buildah copy $container hello-2.12.1.tar.gz /tmp/hello-2.12.1.tar.gz
rm hello-2.12.1.tar.gz

buildah run $container dnf install -y tar gzip gcc make
buildah run $container dnf clean all
buildah run $container tar xvzf /tmp/hello-2.12.1.tar.gz -C /opt

buildah config --workingdir /opt/hello-2.12.1 $container

buildah run $container ./configure
buildah run $container make
buildah run $container make install
buildah run $container hello -v

buildah config --entrypoint /usr/local/bin/hello $container

buildah commit --format docker $container hello:2.12.1

Spack & Nano

Following builds a container including the spack package management system…

spack_version=${1:-0.19}
spack_source_path=/srv/spack
spack_install_root=/opt/spack
container_image=${2:-rockylinux/rockylinux:8}
container=spack-$spack_version
# bootstrap container and install platform dependencies
buildah from --name $container $container_image
buildah run $container -- dnf install -y epel-release dnf-plugins-core
buildah run $container -- dnf config-manager --set-enabled powertools
buildah run $container -- dnf group install -y 'Development Tools'
# install Spack dependencies
buildah run $container \
        -- dnf install -y \
                curl findutils gcc-c++ gcc gcc-gfortran git gnupg2 \
                hostname iproute redhat-lsb-core make patch python3 \
                python3-pip python3-setuptools unzip \
        && python3 -m pip install boto3
# install the Spack source code repository
buildah run $container \
        -- git clone -c feature.manyFiles=true \
                https://github.com/spack/spack.git $spack_source_path
buildah run --workingdir $spack_source_path $container \
        -- git checkout releases/v$spack_version
# add Spack to the environment
tmp_file=$(mktemp)
echo "source /srv/spack/share/spack/setup-env.sh" > $tmp_file
buildah copy $container $tmp_file /etc/default/spack
buildah run $container -- ln -s /etc/default/spack /etc/profile.d/spack.sh
# configure Spack
buildah run --terminal $container -- /bin/bash -ic \
        "spack config --scope system add config:install_tree:root:$spack_install_root"
# build the container and store it to the local registry
buildah commit $container $container

The generic Spack container is then used to build a specific application container…

nano_version=6.3
container=nano-$nano_version
buildah from --name $container localhost/spack-0.19
buildah run $container -- /bin/bash -ic "
      spack install nano@$nano_version
      spack load --sh nano > /etc/profile.d/nano.sh
"
buildah config --entrypoint '/bin/bash -ic nano' $container
buildah commit --rm $container $container
# run the container to launch nano...
podman run -it localhost/nano-6.3