Replace Dockerfile with Buildpacks

Mahdi Mallaki
ITNEXT
Published in
6 min readOct 15, 2023

--

Exploring the Pros and Cons of Replacing Dockerfile with Buildpacks

Photo by Rinson Chory on Unsplash

Introduction

In the world of containerization, where efficiency, speed, and simplicity are paramount, Buildpacks have emerged as a powerful tool that can revolutionize the process of creating Docker images for your projects. Unlike traditional approaches that require laborious Dockerfile creation and maintenance, Buildpacks offer a streamlined and automated solution. With Buildpacks, you can build Docker images effortlessly, regardless of the number of projects you’re dealing with, and without the need for a Dockerfile. Let’s explore how Buildpacks simplify containerization by automatically detecting the programming language and project structure, enabling you to seamlessly integrate Docker image creation into your CI/CD pipeline.

What is Buildpack?

Buildpacks are a handy tool for quickly creating Docker images for your projects without the need for individual Dockerfiles. This means you can efficiently Dockerize multiple projects without writing Dockerfiles for each. Buildpacks automatically detect your project’s programming language and necessary dependencies, such as pom.xml, build.gradle, or requirements.txt files. You only need to run a simple command for each project, making it easy to integrate into your CI/CD pipeline for automated Docker image creation.

Using Dockerfile VS. Buildpacks

Using Buildpacks is a lot easier than using Dockerfiles because, when you use Buildpacks, you don’t need to write a Dockerfile; you simply run a straightforward command to create a Docker image for your project. Another advantage is the handling of multi-staging. When you write a Dockerfile for your project, you have to create a multi-stage Dockerfile, with one stage for building (e.g., for Java projects using Maven or Gradle) and another for running (where only runtime dependencies are required to execute the application). For instance, running a Java application only necessitates a JRE, not a JDK or Maven/Gradle or other build tools.

If you wish to create an efficient Dockerfile for your Java/Spring Boot/Maven project, you need to craft a Dockerfile like this (it has two stages: first build stage and second run stage):

####################### build stage #######################
FROM openjdk:8u342-slim-buster

RUN apt update & apt install -y curl tar bash ca-certificates gnupg

ENV NODE_MAJOR=16
RUN mkdir -p /etc/apt/keyrings && curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg && echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_MAJOR.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list
RUN apt update && apt install nodejs -y

ARG MAVEN_VERSION=3.6.3
ARG BASE_URL=https://archive.apache.org/dist/maven/maven-3/${MAVEN_VERSION}/binaries
RUN mkdir -p /usr/share/maven /usr/share/maven/ref \
&& echo "Downlaoding maven" \
&& curl -fsSL -o /tmp/apache-maven.tar.gz ${BASE_URL}/apache-maven-${MAVEN_VERSION}-bin.tar.gz \
&& echo "Unziping maven" \
&& tar -xzf /tmp/apache-maven.tar.gz -C /usr/share/maven --strip-components=1 \
&& echo "Cleaning and setting links" \
&& ln -s /usr/share/maven/bin/mvn /usr/bin/mvn

WORKDIR /workspace

ADD . /workspace

RUN mvn clean package

RUN mv target/*.jar target/app.jar


####################### run stage #######################
FROM openjdk:8u342-slim-buster

WORKDIR /workspace

COPY --from=0 /workspace/target/app.jar .

ENTRYPOINT ["java", "-jar", "app.jar"]

The above Dockerfile is quite complex, and you need to understand the concept of multi-staging in Docker to comprehend what’s happening in it. However, Buildpacks make it more straightforward and will generate a final Docker image in a different manner, even though the end result is equivalent to an image created by the Dockerfile mentioned above.

Using Buildpacks vs. Dockerfile (The right stream represents the traditional way of using a Dockerfile, while the left stream showcases the new approach of using Docker images with Buildpacks)

When to use Buildpacks?

in the following forms:

  • Repository Write Access: If you don’t have write access to the source code repository and need to push a Dockerfile for your project, you can use a tool that generates a Dockerfile on the fly and builds the Docker image without revealing the actual Dockerfile. This simplifies the process for you.
  • Single Language Source Code: If your source code repository contains code in multiple language, it’s better not to use Buildpacks, as you may need to make extensive customizations to indicate that your project is written in multiple languages.
  • Laziness: Using Buildpacks is incredibly effortless and straightforward, making it an excellent choice when you want to save time and effort!

Install Buildpacks

Installing Buildpacks is straightforward, and you can download and install it from its GitHub releases page at https://github.com/buildpacks/pack:

$ wget https://github.com/buildpacks/pack/releases/download/v0.31.0/pack-v0.31.0-linux.tgz
$ tar -xvzf pack-v0.31.0-linux.tgz
$ sudo mv pack /usr/bin/

Using Buildpacks

Let’s check out a sample project and build a Docker image for it without having to write a Dockerfile for it:

$ git clone https://github.com/paketo-buildpacks/samples
$ cd samples/java/gradle
$ pack build testjavadocker --env BP_JVM_VERSION=17
$ docker run --rm testjavadocker

Buildpacks Issues

  • older Docker versions: I was using Docker version 19.03.5 on my build machine and encountered an issue when trying to use Buildpacks. The following error occurred (Keep in mind that you need Docker version greater than 20 to be able to use the newer versions of the builder-jammy-base image builder.):
$ pack build test --builder=buildpacks/builder-jammy-base:0.1.0
...
===> ANALYZING
Image with name "test" not found
===> DETECTING
======== Output: paketo-buildpacks/leiningen@4.5.1 ========
runtime/cgo: pthread_create failed: Operation not permitted
SIGABRT: abort
PC=0x7f8c2afb8a7c m=0 sigcode=18446744073709551610

goroutine 0 [idle]:
runtime: unknown pc 0x7f8c2afb8a7c
stack: frame={sp:0x7fffb88316a0, fp:0x0} stack=[0x7fffb8032bf8,0x7fffb8831c30)
0x00007fffb88315a0: 0x00007f8c2b13c723 0x00007f8c2b13c723
...
  • Maven Minor Version Customization is Not Possible: The Buildpack paketo-buildpacks/maven does not support changes to minor versions of Maven. For instance, if your project cannot be compiled with the latest minor versions of Maven 3, you will need to use the Maven Wrapper instead. Using the Maven Wrapper is straightforward; you just need to run the following commands to initialize the Maven Wrapper for your project:
$ mvn wrapper:wrapper -Dmaven=3.6.3
$ ./mvnw clean package
  • Buildpacks Environment Variables Are Immutable: Buildpacks, by default, set some default environment variables in the build container. There may be instances when you need to modify or remove these variables. However, you can only change them, not remove them. For example, I encountered difficulties trying to remove the NODE_ENV environment variable from my buildpack’s build container, but it proved impossible and led to numerous issues.
  • Buildpacks for Multi-Language Projects Can Be Challenging: If you’re working on a multi-language project, it’s generally better not to use Buildpacks. While Buildpacks do have support for multi-language projects, customizing them can be time-consuming. For instance, I needed to create a Docker image for a project that used the Spring framework for its backend and Vue.js for its frontend. Both parts were in a single repository, and I had to specify the following parameters to inform Buildpacks that it was a multi-language project:
    - BP_JVM_VERSION: Describing the Java version for my project.
    - BP_NODE_VERSION: Specifying the desired Node.js version for building my project.
    - BP_JAVA_INSTALL_NODE: Requesting Buildpacks to install Node on the build container.
    - BP_NODE_PROJECT_PATH: Indicating the location of my Vue.js files within the project.
    The customization process can be quite involved, especially for multi-language projects.
 pack build test \
--env 'BP_JVM_VERSION=8' \
--env 'BP_MAVEN_BUILD_ARGUMENTS=clean package install -U' \
--env 'BP_NODE_VERSION=16.20.0' \
--env 'BP_JAVA_INSTALL_NODE=true' \
--env 'BP_NODE_PROJECT_PATH=src/main/frontend'
--builder=buildpacks/builder-jammy-base:0.1.0
  • Run without the internet: buildpack is highly dependent on the internet. If your build environment is airgapped (for security reasons), you’ll need to change its downloading source.

Conclusion

In an era where containerization has become a cornerstone of software development, Buildpacks emerge as a game-changing tool that can streamline the process of crafting Docker images for your projects. By eliminating the complexities of traditional Dockerfile creation and maintenance, Buildpacks offer an automated and efficient approach. With the ability to build Docker images effortlessly and without the need for a Dockerfile, they empower developers to handle multiple projects seamlessly. Buildpacks excel at recognizing your project’s programming language and structure, allowing for automatic Docker image creation that can be seamlessly integrated into your CI/CD pipeline.

GitHub

You can find all the diagrams and code used in this article in the following GitHub repository:

Support My Blog ☕️

If you have any feedback or suggestions for improving my code, please leave a comment on this post or send me a message on my LinkedIn. If you enjoy my technical blog posts and find them valuable, please consider buying me a coffee at here. Your support goes a long way in helping me produce more quality articles and content.

--

--