Building OCI Images with Buildah

Published:3 January 2024 - 12 min. read

Arvid Larson Image

Arvid Larson

Read more tutorials by Arvid Larson!

Yearning for a smoother ride in crafting Open Container Initiative (OCI) images? The magic word to untangle your container conundrum is ‘buildah’, your container-building sidekick.

Imagine a world where crafting containers becomes second nature. In this tutorial, you’ll unravel the mysteries of creating OCI images with buildah. Be it from scratch, tweaking existing ones, or even dancing through the world of Dockerfiles.

Get ready to build and export your creations for collaborations!

Prerequisites

Set the stage for your exploration into building OCI images with buildah by ensuring you have the essentials in place. Below is a checklist to guarantee you’re ready for the ride:

  • A Linux machine – This tutorial uses a Debian 12 server.
  • A non-root user with sudo privileges.
  • A Podman or Docker Engine installed on your Linux machine.

Installing buildah on Linux

When building container images, whether from scratch, using existing images, or from the working container, buildah is one you can count on to streamline the process. This tool allows you to create images with OCI or traditional Docker formats.

You can use ample package managers to install buildah in most Linux distributions. But in this tutorial, you’ll install buildah via APT.

To install buildah on your Linux machine, complete the steps below:

1. Open a terminal session, and run the apt update command below to update your system’s repository package index.

💡 Ensure you employ a non-root account with sudo privileges and prefix commands with sudo. This tutorial exclusively uses a root account to sidestep compatibility problems when executing commands, which is not recommended.

apt update
Updating the system’s repository package index
Updating the system’s repository package index

2. Once updated, execute the following command to install buildah while accepting all prompts automatically (-y).

apt install buildah -y
Installing buildah on Linux via APT
Installing buildah on Linux via APT

3. Lastly, run each command below to locate the buildah binary file and verify the buildah --version installed.

# Locate binary path buildah
which buildah

# Checking buildah version
buildah version

Below is the path to buildah (/usr/bin/buildah) with version 1.28.2 installed.

Checking the buildah location and version
Checking the buildah location and version

Building OCI Images from Existing Images

After successfully installing buildah, you’re about to craft your OCI images for your applications, leveraging existing images. The nuances of building OCI images from existing ones are vital to containerization.

You’ll seamlessly integrate and customize existing images, molding them into the perfect fit for your containerized applications.

To build an OCI image from an existing one, follow these steps:

1. Execute the commands below to create a new working container environment from the base image alpine and verify your system’s running containers.

💡 Opt-in for minimal base images, such as Alpine, whenever possible. Doing so helps reduce the overall image size, enhancing efficiency and speeding up deployment times.

This step lets you create a new container called alpine-working-container as a working environment for building OCI images.

# Create a working container from an Alpine image
buildah from alpine

## Checking the list of working container
buildah containers

As shown below, the alpine-working-container is created based on the Docker image alpine and used as a builder.

Creating a working container based on the alpine Docker image

2. Next, run the following buildah run command to install the nginx package (for demonstration purposes) to the alpine-working-container.

buildah run alpine-working-container apk add --no-cache nginx
Installing NGINX to the alpine-working-container
Installing NGINX to the alpine-working-container

3. Once NGINX is installed, run the commands below, which have no output but create a new directory ~/conf directory and a custom index.html file.

# Create ~/conf directory
mkdir -p conf

# Create index.html file
echo "Hello Buildah - adamtheautomator.com" > conf/index.html

4. Now, open the ~/conf/default.conf file in your preferred editor, and add the custom server block configuration below.

This configuration optimizes the NGINX server for efficient content delivery, sets up logging for monitoring, and implements browser-level caching for specified file types.

server {
     # nginx port #
     listen 80;

     # document root #
     root        /var/www/localhost/htdocs;

     # log files
     access_log  /var/log/nginx/access.log;
     error_log   /var/log/nginx/error.log;

     # cache files on browser level #
     # Directives to send expired headers and turn off 404 error logging. #
     location ~* ^.+\\.(ogg|ogv|svg|svgz|eot|otf|woff|mp4|ttf|rss|atom|jpg|jpeg|gif|png|ico|zip|tgz|gz|rar|bz2|doc|xls|exe|ppt|tar|mid|midi|wav|bmp|rtf)$ {
        access_log off; log_not_found off; expires max;
     }
}

5. With configurations all set, run the buildah copy command below to copy the index.html and default.conf files to the alpine-working-container.

These commands provide the alpine-working-container with the required files and configurations to successfully run an NGINX web server.

# Copy index.html file to alpine-working-container
buildah copy alpine-working-container conf/index.html /var/www/localhost/htdocs/index.html

# Copy default.conf file to alpine-working-container
buildah copy alpine-working-container conf/default.conf /etc/nginx/http.d/default.conf
Copying the index.html and default.conf file to the alpine-working-container
Copying the index.html and default.conf file to the alpine-working-container

6. Subsequently, execute the following buildah config commands to configure the default --workingdir and --entrypoint to the alpine-working-container.

The specified working directory and entry point contribute to the seamless execution of NGINX within the container. These commands ensure NGINX starts correctly and operates within the desired context.

# Setting up default WORKDIR to /var/www/localhost/htdocs
buildah config --workingdir /var/www/localhost/htdocs alpine-working-container

# Setting up ENTRYPOINT start command to "/usr/sbin/nginx -g 'daemon off;'"
buildah config --entrypoint "/usr/sbin/nginx -g 'daemon off;'" alpine-working-container
Setting up the WORKDIR and ENTRYPOINT
Setting up the WORKDIR and ENTRYPOINT

7. Next, run the buildah commit command below to build an OCI image called nginx-alpine based on your alpine-working-container and verify the list of images.

# Build an image called nginx-alpine
buildah commit alpine-working-container nginx-alpine

# Checking the list of images
buildah images
Building an OCI image from an existing image
Building an OCI image from an existing image

8. Once you have an OCI image, execute each podman command below to run a new container (nginx) based on nginx-alpine and verify (ps) the list of images.

This step is crucial for testing and validating the functionality of your NGINX containerized application.

# Run the container called nginx based on nginx-alpine
podman run -it -d -p 8080:80 --name nginx localhost/nginx-alpine

# Checking the list of containers
podman ps

You can see below the nginx container with the status Up and exposed port 8080 on the host machine.

Running a container (nginx) with OCI image
Running a container (nginx) with OCI image

9. Lastly, run the curl command below to access the nginx container.

Testing your images locally helps identify and address any issues and prevents potential problems when deploying images in production environments.

curl http://localhost:80808/

Assuming everything works, you’ll get the output below from the nginx container that says Hello Buildah – adamtheautomator.com.

This output proves that your OCI image (nginx-alpine) was successfully built and deployed as a running container.

Verifying the deployed nginx-alpine OCI image
Verifying the deployed nginx-alpine OCI image

Building OCI Images via Dockerfiles

Besides using existing images, buildah is a flexible image-building tool that allows you to build images in multiple ways and environments. One example is using Dockerfiles within a Docker environment that lets you articulate every step of the image creation process.

Dockerfiles provide a standardized, version-controlled, and shareable way to describe the environment and dependencies required for your application.

To build an OCI image via a Dockerfile, carry out the following:

1. Move to the ~/conf directory on your terminal, create a new Dockerfile, and populate the following configuration. Like building OCI images using existing images, this Dockerfile configuration sets up an Alpine Linux-based container with NGINX installed.

# Use the latest Alpine Linux as the base image
FROM alpine:latest

# Set the maintainer information for this Dockerfile
LABEL maintainer="Arvid <[email protected]>"

# Install NGINX and update packages, then clean up to reduce image size
RUN echo "Installing Nginx" \
    && apk add --no-cache nginx \
    && rm -rf /var/cache/apk/*

# Set the working directory inside the container
WORKDIR /var/www/localhost/htdocs

# Copy the custom index.html to the container's web directory
COPY index.html /var/www/localhost/htdocs/index.html

# Copy the custom NGINX configuration file to the appropriate directory
COPY default.conf /etc/nginx/http.d/default.conf

# Expose port 80 to allow external access to the NGINX web server
EXPOSE 80

# Run NGINX in the foreground when the container starts
CMD ["/usr/sbin/nginx", "-g", "daemon off;"]

2. Next, run the buildah build command below to create a new OCI image called nginx-alpine2 based on your Dockerfile.

buildah build -f Dockerfile -t nginx-alpine2 .
Building OCI image via Dockerfile
Building OCI image via Dockerfile

3. Once the process is finished, run the following command to verify your system’s list of OCI images.

buildah images

If the OCI image creation is successful, you’ll see the nginx-alpine2 image, as shown below.

Checking the list of images
Checking the list of images

4. Afterward, execute each command below to run a new container (nginx2) with the base image nginx-alpine2m and check the list of containers (ps).

# Run a new container nginx2 based on nginx-alpine2 image
podman run -it -d -p 8082:80 --name nginx2 nginx-alpine2

# Checking the list of containers
podman ps

You can see below that the nginx2 container is up and with exposed port 8082 on the host machine.

Running a new container (nginx2) with OCI image built via Dockerfile
Running a new container (nginx2) with OCI image built via Dockerfile

5. Lastly, run the following command to check the nginx2 container.

curl http://localhost:8082

An output that says Hello Buildah – adamtheautomator.com displays, like in the screenshot below.

This output confirms that building OCI images via Dockerfile is also viable.

Accessing the nginx2 container
Accessing the nginx2 container

Building OCI Images from Scratch

Dockerfiles provide a structured and reproducible way to define images, which works great. But did you know you can also tailor every aspect of your container environment to meet your unique requirements?

Building OCI images from scratch offers the ultimate flexibility. For instance, you can reduce the size of the final image by installing only packages needed for your application.

To build an OCI image from scratch, proceed with the following:

1. Run the command below to install debootstrap on your system. debootstrap is your go-to companion for crafting minimal Debian environments from the ground up.

This tool meticulously assembles the essentials by fetching and unpacking Debian archives into a designated subdirectory. The result? A minimal Debian environment that forms the foundation for your custom configurations and applications.

apt install debootstrap
Installing the debootstrap package for crafting minimal Debian environments
Installing the debootstrap package for crafting minimal Debian environments

2. With debootstrap installed, execute the following commands to create a container ($newcontainer) with the most minimal base image available (scratch).

# Create a minimal working container using the 'scratch' base image
newcontainer=$(buildah from scratch)

# Verify the creation of the new working container
buildah containers

As seen in the following, a new container called working-container is created.

Creating a container from scratch

3. Next, run the below commands to achieve the following:

  • Retrieve and store the mount point of the working-container filesystem in the environment variable scratchmnt.
  • Print (echo) the obtained mount point ($scratchmnt).
scratchmnt=$(buildah mount $newcontainer)
echo $scratchmnt

The information below can be crucial when working with the contents of the container, allowing you to manipulate files and directories from the host system.

Capturing the mount point of the working-container filesystem
Capturing the mount point of the working-container filesystem

4. Now, execute the debootstrap command below to install the Debian bookworm base system to the $scratchmnt directory where working-container is mounted.

debootstrap bookworm $scratchmnt
Installing the Debian base system to the scratch container
Installing the Debian base system to the scratch container

5. Once installed, run the following commands to execute the bash shell inside the container specified by the variable $newcontainer and exit the container.

# Execute the bash shell
buildah run $newcontainer bash
# Or press Ctrl+D to exit the container
exit
Executing an interactive Bash shell within a container (working-container)
Executing an interactive Bash shell within a container (working-container)

6. Create a new sample application called hello.sh in your preferred editor and insert the following code, which prints a message 10 times.

#!/usr/bin/env bash
for i in {1..10}; do
   echo "Hello Buildah - adamtheautomator.com"
done

7. Execute the following chmod command, which has no output if successful, to make the *hello.sh* file executable.

chmod +x hello.sh

8. Now, run the command below to copy the hello.sh file to the working-container directory (/usr/bin). Doing so ensures the script is executable and accessible within the container.

buildah copy $newcontainer ./hello.sh /usr/bin
Copying the hello.sh script to the /usr/bin directory
Copying the hello.sh script to the /usr/bin directory

9. Once copied, run the buildah config command below to set the default command (--cmd) to be executed when the container starts.

The default command in this case is /usr/bin/hello.sh, which runs your hello.sh script.

buildah config --cmd /usr/bin/hello.sh $newcontainer

10. Further, execute the following commands to create a new OCI image (commit) called nginx-debian based on the container working-container and verify the list of images.

# Commit the changes to create a new image named 'nginx-debian'
buildah commit $newcontainer nginx-debian

# List available container images, including 'nginx-debian'
buildah images

In the output below, the nginx-debian image is available among other images.

Creating a new OCI image (nginx-debian) from scratch
Creating a new OCI image (nginx-debian) from scratch

11. Now, run the following commands to unmount and delete (rm) working-container.

These commands have no output, but this process ensures resources associated with the working-container are properly released and cleaned up.

buildah unmount working-container
buildah rm working-container

12. Finally, run the command below to perform the following:

  • Create a new container based on the nginx-debian image and run that container.
  • Removes (--rm) the container automatically once execution is complete.

This approach is a common practice for short-lived containers where the goal is to execute a task and discard the container afterward. The last thing you’d want is these containers sticking around and cluttering your system—driving you nuts.

podman run --rm nginx-debian

As shown below, the container executes the start command (/usr/bin/hello.sh) and prints Hello Buildah – adamtheautomator.com 10 times.

This output demonstrates the successful creation and functionality of your nginx-debian container image.

Executing a disposable container with automatic cleanup
Executing a disposable container with automatic cleanup

Exporting and Uploading OCI Images to Image Registries

After meticulously crafting containers tailored to your applications, you must ensure they are securely stored and readily accessible for deployment. How? You’ll export and upload these masterpieces to an Image Registry.

buildah allows you to upload OCI images virtually anywhere, offering the flexibility to:

  • Export images to a designated directory or as an archive file.
  • Upload images to public and private image registries, such as Docker Registry.”

To export and upload OCI images with buildah, execute the following steps:

mkdir -p ~/exported/{docker,oci}

2. Next, execute the buildah push command below to export the nginx-alpine image with Docker format to the ~/exported/Docker directory.

Exporting images makes them transportable and shareable with others who may want to use the same image.

buildah push nginx-alpine dir:./exported/docker/
Exporting an OCI image (nginx-alpine) to a local directory with Docker format

3. Subsequently, run the following command to export (push) the nginx-alpine image with the oci format to the ~/exported/OCI directory and verify the OCI image.

# Export the nginx-alpine image with the OCI format to the ~/exported/OCI directory
buildah push nginx-alpine oci:./exported/OCI/:nginx-alpine:latest
# List the contents (ls) of the ~/exported/OCI/* directory
ls ./exported/OCI/
# Display the contents (cat) of the index.json file within the exported OCI image
cat ./exported/OCI/index.json

The output below shows three items in the ~/exported/OCI/ directory, including the index.json file. This metadata file contains essential information about the exported image, such as its layers, configuration, and other relevant details.

Exporting an OCI image (nginx-alpine) to a local directory with OCI format
Exporting an OCI image (nginx-alpine) to a local directory with OCI format

4. Now, run each command below to export the nginx-alpine image as an archive in Docker (docker-archive) and OCI format (oci-archive).

# Export as an archive with Docker format
buildah push nginx-alpine docker-archive:./exported/nginx-alpine.gzip:nginx-alpine:latest
# Export as an archive with OCI format
buildah push nginx-alpine oci-archive:./exported/nginx-alpine.zstd:nginx-alpine:latest
# List the contents of the ~/export directory
ls ~/exported/
Exporting an OCI image as an archive with Docker and OCI format
Exporting an OCI image as an archive with Docker and OCI format

5. Ultimately, execute the below command to upload the image to the Docker Registry.

Ensure you change the username and password placeholders with your Docker credentials.

buildah push --creds=username:password nginx-alpine docker://username/nginx-alpine:latest
Uploading an OCI image to the Docker Registry
Uploading an OCI image to the Docker Registry

💡 Optionally, run the below commands to upload the nginx-alpine images to a private and local image registry.

Uploading image to private Docker Registry

buildah push nginx-alpine docker://registry.example.com/nginx-alpine:latest

Uploading an image to local image registry

buildah push --tls-verify=false nginx-alpine localhost:5000/nginx-alpine

Once uploaded, check the image on the Repositories tab on the Docker Registry.

In this example, the nginx-alpine image is uploaded successfully, as shown below.

Checking the uploaded OCI image on the Docker Registry
Checking the uploaded OCI image on the Docker Registry

Conclusion

Throughout this tutorial, you’ve navigated the intricate landscape of building OCI images with buildah. You’ve explored crafting containers from existing images, via Dockerfiles, and even from scratch. Bravely, you ventured into the wild realm of exporting and uploading your creations to Image Registries.

At this point, your containers are like rockstars, ready to hit the stage in any environment you throw at them. Armed with the knowledge of constructing, customizing, and distributing OCI images, the possibilities are expansive.

Why not consider integrating these skills into your everyday workflows? Would you be interested in a project like Skopeo for managing OCI images? Experiment with tweaking existing images, fine-tune your Dockerfiles for precision, and dare to create from scratch!

Hate ads? Want to support the writer? Get many of our tutorials packaged as an ATA Guidebook.

Explore ATA Guidebooks

Looks like you're offline!