Containerize an Application With Podman and Buildah

Containerize an Application With Podman and Buildah

Docker revolutionized the software industry in terms of how we build, develop, and ship software. But nothing is forever, and it seems that the software community has begun having conversations that will/could eventually lead to people moving away from Docker.

I wouldn't go into details about what is wrong with Docker. I'm sure there's plenty of great content out there that goes into great depths about this topic. I would just add one thought to that: People are quick to write off old/existing technologies whenever there's a new contender. I would avoid making outrageous claims like “Docker is dead.”

Moving on...

Podman and Buildah

What I would like to explore a bit today are the new open source contenders, Podman and Buildah (terrible name), being somewhat aggressively pushed by Red Hat.

Quick Overview

podman replaces the docker command. All your scripts that work with docker should work with podman as well. Images are compatible between both the tools, thanks to Open Container Initiative.

buildah replaces docker build. You can still use Dockerfiles to build containers with Buildah, but Buildah offers a powerful CLI, which means that not only the traditional Dockerfile approach would be replaced by Bash scripts, you can also build containers from your terminal, which makes it a lot easier to write your build script step by step.

How do I write a Buildah script?

Before we begin, make sure you have Podman and Buildah installed. You can click these links for installation instructions.

For this article, I will use a simple “hello world” Spring Boot application. You can easily generate an empty Spring Boot app with Spring Initializr.

In this example, we will choose the “Gradle Project” option and add the “Spring Web” dependency.

Once you have generated the project, just add a super simple controller that implements an endpoint returning a “hello world” string:

// HelloWorldController.java
package com.aadilp.springpodman;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloWorldController {
    @GetMapping("/")
    public String helloWorld() {
        return "Hello world!";
    }
}

Now we are ready to containerize this application. Let's start with creating a Dockerfile since this is a format we are familiar with, and it will later aid us in writing a Buildah script.

## Build the project
FROM gradle:6.7.1-jdk11-hotspot AS builder

WORKDIR /app

COPY build.gradle settings.gradle /app/
COPY gradle /app/gradle
COPY src /app/src

RUN gradle clean build -x test

## Build container to run the project
FROM adoptopenjdk:11-jre-hotspot

WORKDIR /app

## Replace springpodman below with your project name
COPY --from=builder /app/build/libs/springpodman*.jar service.jar

EXPOSE 8080

ENTRYPOINT exec java -jar service.jar

In a nutshell, this Dockerfile uses the gradle image to build the project, which generates a jar file. Then it pulls in the adoptopenjdk image and copies the jar file into the container. Then, it sets the port 8080 to be exposed at runtime, and sets the entrypoint to execute the jar file.

As stated earlier, Buildah allows you to use Dockerfile with the buildah bud command, where bud is an alias for build-using-dockerfile. So you can already build containers without learning anything new.

But the learning curve for the Buildah CLI is not actually huge, as you'll notice below. Let's start building with Buildah directly from the terminal, and once we are confident with the commands, we can put them into a Bash script to reuse later. So why not just create a Bash script right away? Building from the terminal allows us not to redo the same steps over and over again in case an instruction fails, so we will not only be saving time by not running the script from the beginning over and over again, but also be confident that the final version we put into a Bash script will work.

For the sake of simplicity, let's run the next few commands as superuser to avoid any permission errors. You can simply run sudo -i or sudo -s to switch to superuser.

Let's start with the FROM instruction. Type this into your terminal:

> builder=$(buildah from gradle:6.7.1-jdk11-hotspot)

As you can see, the buildah from command is very similar. This will make the $builder variable available to us which holds the ID of the newly created container. We will need this container ID for the next steps.

Let's set the working directory using buildah config:

> buildah config --workingdir='/app' $builder

As you can easily guess, Buildah uses the container ID argument to determine which container an action should apply to.

The buildah copy and buildah run commands are also very familiar looking:

> buildah copy $builder build.gradle settings.gradle /app/
> buildah copy $builder src /app/src
> buildah copy $builder gradle /app/gradle
> buildah run $builder -- gradle clean build -x test

If you were able to run these commands successfully, you have already managed to build the Java project in a container using Buildah. Now let's set up the container to run this project, let's call it jdkcontainer:

> jdkcontainer=$(buildah from adoptopenjdk:11-jre-hotspot)

The next instruction that we need to translate is not as straightforward with Buildah as it is with Dockerfile instructions. With Dockerfile, if you need to copy something from one container to another, you can just use the --from flag with the COPY instruction.

With Buildah, we first need to mount the $builder container's root file system in a location accessible from the host:

> buildermnt=$(buildah mount $builder)

The $buildermnt variable will hold the location where the $builder container's file system was mounted. This is usually somewhere inside /var/lib/containers/.

Now, we are ready to copy:

> buildah copy $jdkcontainer $buildermnt/app/build/libs/springpodman*.jar service.jar

Don't forget to unmount the container's file system once you have successfully copied what you need:

> buildah unmount $builder

The last two instructions from the Dockerfile, EXPOSE and ENTRYPOINT, are straightforward in Buildah:

> buildah config --port 8080 $jdkcontainer
> buildah config --entrypoint 'exec java -jar service.jar' $jdkcontainer

But we are not done yet. We need to create an image from the $jdkcontainer. So this will be our last step. Let's name it spring-podman:

> buildah commit $jdkcontainer spring-podman

And that's it! Not complicated at all. Just type exit to logout from the superuser.

You can list the available images to confirm your new image was created.

> sudo buildah images
REPOSITORY                       TAG                   IMAGE ID       CREATED          SIZE
localhost/spring-podman          latest                abcd1234ab12   22 seconds ago   269 MB

Now that we are confident that our Buildah commands work, we can just copy those commands and put them into a Bash script. Let's call it buildah-build-service.sh:

#!/usr/bin/env bash

## Build the project
builder=$(buildah from gradle:6.7.1-jdk11-hotspot)

buildah config --workingdir='/app' $builder

buildah copy $builder build.gradle settings.gradle /app/
buildah copy $builder src /app/src
buildah copy $builder gradle /app/gradle

buildah run $builder -- gradle clean build -x test

## Build container to run the project
jdkcontainer=$(buildah from adoptopenjdk:11-jre-hotspot)

buildah config --workingdir='/app' $jdkcontainer

buildermnt=$(buildah mount $builder)

buildah copy $jdkcontainer $buildermnt/app/build/libs/springpodman*.jar service.jar

buildah unmount $builder

buildah config --port 8080 $jdkcontainer

buildah config --entrypoint 'exec java -jar service.jar' $jdkcontainer

buildah commit $jdkcontainer spring-podman

Next time, you can just run sudo ./buildah-build-service.sh to build the container. Don't forget to make the script executable by running chmod +x buildah-build-service.sh.

Now let's run this container using Podman:

> sudo podman run -p 8080:8080 --name service spring-podman

You should be able to see “Hello world” on localhost:8080.

Congratulations! You have successfully containerized a Spring Boot application using Podman and Buildah.

In case something is not clear, you can access all this code here: github.com/aadilp/spring-podman

Feel free to comment or create a pull request if you face any issues. I'll be happy to receive feedback on how we can make it even better.

Thank you, and hope you enjoyed it!