Before we get into the business of building images with Buildah, I believe a little clearing of the bush is worthwhile because the field of containerization can become a maze of jargon within the first opening paragraphs in most articles. First let us get to understand what OCI, Buildah, and Docker are all about.
Open Container Initiative (OCI)
Well, referenced from the OCI Offical Site, the Open Container Initiative (OCI) launched on June 22nd 2015 by Docker, CoreOS and other partners is a lightweight project, for the express purpose of creating open industry standards around container formats and runtime.
The OCI currently contains two specifications: the Runtime Specification (runtime-spec) and the Image Specification (image-spec). The Runtime Specification outlines how to run a “filesystem bundle” that is unpacked on disk. A “filesystem bundle” is a set of files organized in a certain way, and containing all the necessary data and metadata for any compliant runtime (eg Docker and CRI-O) to perform all standard operations against it.
At a high-level an OCI implementation would download an OCI Image then unpack that image into an OCI Runtime filesystem bundle. At this point the OCI Runtime Bundle would be run by an OCI Runtime (eg Docker and CRI-O). Now moving to Buildah.
Buildah
Buildah is a command line tool that can be used to build images compliant with Open Container Initiative (OCI). It can be used with Docker, Podman, Kubernetes or any of your favourite container tool.
Buildah’s commands are similar to all of the commands that you can find in a Dockerfile. Buildah’s goal is also to provide a lower level coreutils interface to build container images, allowing people to build containers without requiring a Dockerfile. The most powerful way to use Buildah is to write Bash scripts for creating your images—in a similar way that you would write a Dockerfile without requiring a daemon.
You can achieve the following and much more with the buildah command
- Build a container image from a Dockerfile (using buildah bud).
- Build a container from another base image (buildah from ) or from complete scratch (using buildah from scratch)
- Inspecting a container or image (using buildah inspect)
- Mount a container: Mount a container’s root filesystem to add or change the content (buildah mount).
- Create a new container layer (using buildah commit).
- Unmount a container: Unmount a mounted container (buildah umount).
- Delete a container (using buildah rm) or an image (using buildah rmi)
In plain English, OCI sets out the rules for building images and how runtimes should consume it whild Buildah takes those rules set out and used to Build container images. Now what is Docker all about?
Docker
It was hard to keep it brief but reading through will definitely help
Before version 1.11 of Docker, the implementation was a monolithic daemon. The monolith did everything as one package such as downloading container images, launching container processes, exposing a remote API, and acting as a log collection daemon, all in a centralized process running as root (Source coreos). Such a centralized architecture has some benefits when it comes to deployment but unearths other fundamental problems. An example is that it does not follow best practices for Unix process and privilege separation. Moreover, the monolithic implementation makes Docker difficult to properly integrate with Linux init systems such as upstart and systemd https://coreos.com/rkt/docs/latest/rkt-vs-other-projects.html#rkt-vs-docker. This led to the splitting of Docker into different parts as can be shown on the below opening paragraph after Docker 1.11 was launched.
“We are excited to introduce Docker Engine 1.11, our first release built on runC ™ and containerd ™. With this release, Docker is the first to ship a runtime based on OCI technology, demonstrating the progress the team has made since donating our industry-standard container format and runtime under the Linux Foundation in June of 2015. Source Docker”.
According to them (Docker), splitting Docker up into focused independent tools mean more focused maintainers and ultimately better quality software.
Since then containerd now handles the execution of containers which was previously done by docker daemon itself. This is the exact flow, the user runs commands from docker-cli. Docker-cli talks to the Docker daemon. The Docker daemon(dockerd) listens for requests and manages the lifecycle of the container via containerd which it contacts. containerd takes the request and starts a container through runC and does all the container life-cylces within the host. runc in brief is a CLI tool for spawning and running containers according to the OCI specification.
How To Build OCI & Docker Container Images With Buildah
Run the command below on your Linux box to install Buildah. We shall install Podman alongside it so that we can use it to run our image from scratch.
### Ubuntu / Debian ###
sudo dnf update
sudo dnf -y install buildah podman
### RHEL / CentOS / Rocky / AlmaLinux ###
sudo apt update
sudo apt install podman buildah
Check if it was installed successfully
### RHEL / CentOS / Rocky / AlmaLinux ###
$ rpm -q buildah
buildah-1.23.1-2.module+el8.5.0+735+2f243138.x86_64
### Ubuntu / Debian ###
$ apt policy buildah
buildah:
Installed: 1.23.1+ds1-2
Candidate: 1.23.1+ds1-2
Version table:
*** 1.23.1+ds1-2 500
500 http://ke.archive.ubuntu.com/ubuntu jammy/universe amd64 Packages
100 /var/lib/dpkg/status
Creating an OCI image from scratch with Buildah
Here, we are going to build an image without anything but a small amount of container metadata and then add everything it needs to run a simple Apache webserver. Doing this allows you to build your container image block by block.
As it had been explained earlier, we use buildah scratch command to achieve this. In order to avoid problems, we shall proceed from here using “root” user account.
brandnewcontainer=$(buildah from scratch)
$ buildah containers
CONTAINER ID BUILDER IMAGE ID IMAGE NAME CONTAINER NAME
e6c2f7b0b567 * scratch working-container
### You can also use Podman ###
$ podman images
The above command creates an empty container (no image). Mount the empty container as follows:
$ scratchmnt=$(buildah mount $brandnewcontainer)
$ echo $scratchmnt
/var/lib/containers/storage/overlay/40e7215211b47e8de47991d0dc7be07e0b1b4f48eda25ebaf6ff8ff46c466be5/merged
Buildah mount command allows you to mount the container’s root filesystem, which gives you access to it from the host.
Next, install packages into the scratch image.
sudo yum -y group install "Minimal Install" --releasever=8 --installroot=$scratchmnt
After the packages have been installed. We can now unmount the image and run it as an independent container becuse it has everything it needs to be independent.
$ buildah umount $brandnewcontainer
e6c2f7b0b5679133ad6e0ad6bd74164dac7f357f0076cf6cc819f9ed664236d5
To run the image as a continer, supply the command below and you should be able to land right into the arms of your new container that you have created from scratch. You can install other applications inside there.
$ buildah run $brandnewcontainer bash
[root@e6c2f7b0b567 /]# ls
bin dev home lib64 mnt proc run srv tmp var
boot etc lib media opt root sbin sys usr
As you can see from above, all directories are available as you would expect in a standard container. We can install Apache to test if everything is working fine.
[root@e6c2f7b0b567 /]# yum install httpd -y
Add a simple html page in the default Apache document root and exit from the container
[root@e6c2f7b0b567 /]# echo "Testing Apache." > /var/www/html/index.html
[root@e6c2f7b0b567 /]# exit
Back to our host system, 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 localhost/firstapache:latest
Getting image source signatures
Copying blob b34ab2705c68 done
Copying config a0c546bc39 done
Writing manifest to image destination
Storing signatures
a0c546bc39271565946d11a843979e017aae73e2b792cc5d9ca589661f427543
After buildah is done committing the image, let us now brandish Podman to run it as follows.
First, check all the images available in the system and to get the ID of the ones we are interested in.
$ podman images
REPOSITORY TAG IMAGE ID CREATED SIZE
localhost/firstapache latest a0c546bc3927 8 minutes ago 1.68 GB
Now run the image
$ podman run -p 8080:80 -d --name apache-server a0c546bc3927
b1d29603542c8f91a289127cee4fa9157962f8fcdda9baa82b335e08a94c0992
To make sure that our Apache webserver is serving the page we added earlier, we must do something. You guessed it, we can either curl or even use our browser to see the results. Remember that port 80 in the container has been bonded to port 8080 on the system. We can test as shown below using curl.
$ curl -ik "localhost:8080"
HTTP/1.1 200 OK
Date: Thu, 23 Apr 2020 22:21:54 GMT
Server: Apache/2.4.37 (centos)
Last-Modified: Thu, 23 Apr 2020 22:02:43 GMT
ETag: "f-5a3fc6872e6c0"
Accept-Ranges: bytes
Content-Length: 15
Content-Type: text/html; charset=UTF-8
Testing Apache.
As you can lucidly witness, the webserver is listening and has returned a response we were expecting. It is working.
Buildah comes with a lot more tools at your disposal. You can delete images and containers, inspect images and containers among others. To inspect as an example, list all images to get their IDs using “buildah images” command, then snatch the ID of the one you would wish to inspect then run:
$ buildah inspect a0c546bc3927
{
"Type": "buildah 0.0.1",
"FromImage": "localhost/firstapache:latest",
"FromImageID": "a0c546bc39271565946d11a843979e017aae73e2b792cc5d9ca589661f427543",
"FromImageDigest": "sha256:2060eb441d905934f0aa1749b0d9ed065fd464b30483a963e5719b27836c844d",
"Config": "{\"created\":\"2020-04-23T22:09:50.515019063Z\",\"architecture\":\"amd64\",\"os\":\"linux\",\"config\":{\"ExposedPorts\":{\"80/tcp\":{}},\"Cmd\":[\"/usr/sbin/httpd\",\"-DFOREGROUND\"],\"Labels\":{\"io.buildah.version\":\"1.11.6\"}},\"rootfs\":{\"type\":\"layers\",\"diff_ids\":[\"sha256:b34ab2705c68989b396dfc33dd5174f34b14890242aae93397d628787d2a6a94\"]},\"history\":[{\"created\":\"2020-04-23T22:09:50.515019063Z\",\"created_by\":\"/bin/sh\"}]}",
"Manifest": "{\"schemaVersion\":2,\"config\":{\"mediaType\":\"application/vnd.oci.image.config.v1+json\",\"digest\":\"sha256:a0c546bc39271565946d11a843979e017aae73e2b792cc5d9ca589661f427543\",\"size\":396},\"layers\":[{\"mediaType\":\"application/vnd.oci.image.layer.v1.tar\",\"digest\":\"sha256:b34ab2705c68989b396dfc33dd5174f34b14890242aae93397d628787d2a6a94\",\"size\":1684295168}]}",
"Container": "",
"ContainerID": "",
"MountPoint": "",
"ProcessLabel": "",
"MountLabel": "",
"ImageAnnotations": null,
"ImageCreatedBy": "",
"OCIv1": {
"created": "2020-04-23T22:09:50.515019063Z",
"architecture": "amd64",
"os": "linux",
"config": {
"ExposedPorts": {
"80/tcp": {}
},
"Cmd": [
"/usr/sbin/httpd",
"-DFOREGROUND"
],
"Labels": {
"io.buildah.version": "1.11.6"
}
},
"rootfs": {
"type": "layers",
"diff_ids": [
"sha256:b34ab2705c68989b396dfc33dd5174f34b14890242aae93397d628787d2a6a94"
]
},
"history": [
{
"created": "2020-04-23T22:09:50.515019063Z",
"created_by": "/bin/sh"
}
]
},
"Docker": {
"created": "2020-04-23T22:09:50.515019063Z",
"container_config": {
"Hostname": "",
"Domainname": "",
"User": "",
"AttachStdin": false,
"AttachStdout": false,
"AttachStderr": false,
"ExposedPorts": {
"80/tcp": {}
},
It should produce copious amount of information for you to digest.
Conclusion
Buildah is a cool tool that can build images from scratch or using another image as its base. You can as well modify another image as you wish which makes it quite versatile and flexible. Thus far, we have covered building from scratch and we appreciate you staying till the end.
You may also be excited by:
- Setup Docker Container Registry with Podman & Let’s Encrypt SSL
- How To Install Podman on Ubuntu
- Install Docker CE on RHEL 7 Linux
- Top Minimal Container Operating Systems for running Kubernetes
- Install CRI-O Container Runtime on CentOS 8 / CentOS 7
- How To run Docker Containers using Podman and Libpod
- Docker vs CRI-O vs Containerd