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!