CNCF Buildpacks come in a variety of flavors; just look at what the pack
CLI (as of version 0.16) documents by default:
1 2 3 4 5 6 7 8 9 |
pack inspect-builder [...] Suggested builders: Google: gcr.io/buildpacks/builder:v1 Ubuntu 18 base image with buildpacks for .NET, Go, Java, Node.js, and Python Heroku: heroku/buildpacks:18 heroku-18 base image with buildpacks for Ruby, Java, Node.js, Python, Golang, & PHP Paketo Buildpacks: paketobuildpacks/builder:base Ubuntu bionic base image with buildpacks for Java, .NET Core, NodeJS, Go, Ruby, NGINX and Procfile Paketo Buildpacks: paketobuildpacks/builder:full Ubuntu bionic base image with buildpacks for Java, .NET Core, NodeJS, Go, PHP, Ruby, Apache HTTPD, NGINX and Procfile Paketo Buildpacks: paketobuildpacks/builder:tiny Tiny base image (bionic build image, distroless-like run image) with buildpacks for Java Native Image and Go [...] |
Scope of the article
June 2023 update!
Hello reader! Since 2021, a lot has happened in the Buildpacks community, and while this article still provides valuable information 2 years later, I suggest you read the official tutorials that have been created since then.
Here a a few pointers:
- Officially maintained Paketo Java Howto https://paketo.io/docs/howto/java/
- Officially maintained Paketo Configuration and binding Howto https://paketo.io/docs/howto/configuration/
- Officially maintained SBOM Howto https://paketo.io/docs/howto/sbom/
- And finally, to get experience with latest features, check out the samples Github Repository, where you’ll find examples for all kind of use cases (native, CA certificates, etc.) https://github.com/paketo-buildpacks/samples/tree/main/java
Need more? Contact the community!
Check out the Github repository where I share all those code samples !
This article will focus on how to build a Java maven
based project, but many parts of it could be re used for other languages.
This article won’t cover the Google builder in particular, since, even if it’s a standard CNCF buildpack, it is focused on Google Cloud deployment (including function); the documentation is pretty extensive though, check it out.
Also, this article won’t cover the Heroku builder, also a standard CNCF buildpack, also focused on a PaaS vendor, Heroku. Their java
support documentation can be found on their website.
This article will mainly list examples on how to customize the standard and also paketo builpacks behavior; since they provide, according to me, the greatest level of flexibility (a lot of buildpacks can be swapped in / out and each of them has a specific focus), a non vendor approach (paketo buildpacks were launched by Pivotal Cloud Foundry but do not target Pivotal products specifically) and also because I found some use cases difficult to come by, hence this blog post !
One interesting thing to note is that while all 3 builders listed by the pack
CLI are CNCF buildpack builders, they all have their different way to customize the build:
- Google builder seems to lean on environment variable to override defaults
- Heroko builder uses a file named
system.properties
- Paketo uses a mix of environment variables and bindings
Paketo buildpacks: choose a different JVM version than the default one
At the time of writing, Java 11 is the default version for the Bellsoft Liberica buildpack.
But you can change that, setting the environment variable named BP_JVM_VERSION
to 15.*
for example; the official documentation describes in great detail what other environment variables are available.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
> pack build demo:0.0.1-SNAPSHOT --env BP_JVM_VERSION="15.*" --builder=paketobuildpacks/builder:base [...] Paketo BellSoft Liberica Buildpack 6.2.0 https://github.com/paketo-buildpacks/bellsoft-liberica Build Configuration: $BP_JVM_VERSION 15.* the Java version Launch Configuration: $BPL_JVM_HEAD_ROOM 0 the headroom in memory calculation $BPL_JVM_LOADED_CLASS_COUNT 35% of classes the number of loaded classes in memory calculation $BPL_JVM_THREAD_COUNT 250 the number of threads in memory calculation $JAVA_TOOL_OPTIONS the JVM launch flags BellSoft Liberica JDK 15.0.2: Contributing to layer Downloading from https://github.com/bell-sw/Liberica/releases/download/15.0.2+10/bellsoft-jdk15.0.2+10-linux-amd64.tar.gz [...] |
Paketo buildpacks: choose a different JVM than Bellsoft Liberica
By default, the paketo builder will choose a Bellsoft Liberica JVM;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
> pack build demo:0.0.1-SNAPSHOT --builder=paketobuildpacks/builder:base ... ===> DETECTING 7 of 18 buildpacks participating paketo-buildpacks/ca-certificates 1.0.1 paketo-buildpacks/bellsoft-liberica 6.2.0 paketo-buildpacks/maven 3.2.1 paketo-buildpacks/executable-jar 3.1.3 paketo-buildpacks/apache-tomcat 3.2.0 paketo-buildpacks/dist-zip 2.2.2 paketo-buildpacks/spring-boot 3.5.0 ... Paketo BellSoft Liberica Buildpack 6.2.0 https://github.com/paketo-buildpacks/bellsoft-liberica Build Configuration: $BP_JVM_VERSION 11.* the Java version |
but you can override this default behavior specifying another paketo supported JVM buildpack:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
> pack build demo:0.0.1-SNAPSHOT --builder=paketobuildpacks/builder:base --buildpack=paketo-buildpacks/ca-certificates,gcr.io/paketo-buildpacks/adopt-openjdk,paketo-buildpacks/maven,paketo-buildpacks/executable-jar,paketo-buildpacks/apache-tomcat,paketo-buildpacks/dist-zip,paketo-buildpacks/spring-boot ... ===> DETECTING paketo-buildpacks/ca-certificates 1.0.1 paketo-buildpacks/adopt-openjdk 6.2.0 paketo-buildpacks/maven 3.2.1 paketo-buildpacks/executable-jar 3.1.3 paketo-buildpacks/apache-tomcat 3.2.0 paketo-buildpacks/dist-zip 2.2.2 paketo-buildpacks/spring-boot 3.5.0 ... Paketo AdoptOpenJDK Buildpack 6.2.0 ... |
A bit verbose, I got to admit; but you could imagine creating your own meta-buildpack that would reference all those buildpacks: this is what the paketo team did with the java
meta buildpack.(also called buildpack-group
)
Paketo buildpacks: use custom CA certificates
For every need, there’s a buildpack! The ca-certificates
buildpack will help you ship your own ca certificate in your image.
You’ll need to create a binding
folder with the CA public certificate and a type
file with only the string content ca-certificates
1 2 3 4 |
> ls binding/ca-certificates AnthonyOrganization.crt type > cat binding/ca-certificates/type ca-certificates |
Add certificates binding at runtime
If your image was already built, then you can add your ca-certificates binding at runtime
1 2 3 4 |
> docker run --env SERVICE_BINDING_ROOT=/platform/bindings --volume $PWD/binding/ca-certificates:/platform/bindings/my-certificates -p 8080:8080 -it demo:0.0.1-SNAPSHOT [...] Added 1 additional CA certificate(s) to system truststore [...] |
Add certificates binding at build time
If you can though, it’s probably better to add your public CA certificate at build time, so that your container will accept your CA no matter how the container was called
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
> pack build demo:0.0.1-SNAPSHOT --env SERVICE_BINDING_ROOT=/platform/bindings --volume $PWD/binding/ca-certificates/:/platform/bindings/my-certificates --builder=docker.io/paketobuildpacks/builder:base [...] [builder] Paketo CA Certificates Buildpack 1.0.1 [builder] https://github.com/paketo-buildpacks/ca-certificates [builder] Launch Helper: Reusing cached layer [builder] CA Certificates: Contributing to layer [builder] Added 1 additional CA certificate(s) to system truststore [builder] Writing env.build/SSL_CERT_DIR.append [builder] Writing env.build/SSL_CERT_DIR.delim [builder] Writing env.build/SSL_CERT_FILE.default [...] docker run -it -p 8080:8080 demo:0.0.1-SNAPSHOT [...] Adding 138 container CA certificates to JVM truststore [...] |
Paketo buildpacks: use a custom settings.xml
In case you need access to private Maven repository to get your project 3rd party libraries, you’ll need to configure your maven
buildpack to pick up your custom settings.xml
; once again, bindings
to the rescue !
First, provide your settings.xml
and a type
file with content equals to the string maven
in a binding
folder
1 2 3 4 |
> ls binding/maven-settings settings.xml type > cat binding/maven-settings/type maven |
Of course, you’ll need to provide this binding to the pack
CLI:
1 |
> pack build demo:0.0.1-SNAPSHOT --env BP_MAVEN_BUILD_ARGUMENTS="-Dmirror-password=XXX package -DskipTests" --env SERVICE_BINDING_ROOT=/platform/bindings --volume $PWD/binding/maven-settings:/platform/bindings/maven-settings --builder=paketobuildpacks/builder:base |
You’ll notice the additional environment variable named BP_MAVEN_BUILD_ARGUMENTS
being used here to provide the password to access the private repository, as well as the maven goal. Here are the relevant parts of the settings.xml
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
<servers> <server> <id>anthony-jfrog-mirror</id> <username>anthony.dahanne@gmail.com</username> <password>${mirror-password}</password> </server> </servers> <mirrors> <mirror> <id>anthony-jfrog-mirror</id> <name>Anthony's Central and JCenter Mirror</name> <url>https://anthonydahanne.jfrog.io/artifactory/all/</url> <mirrorOf>*</mirrorOf> </mirror> </mirrors> |
How to extend your buildpack run image
It’s possible that you don’t want to rely on your buildpack provider run image
(the base image of your app image); for example because it’s too heavy for your needs or because there’s a missing tool or library that your app will require at runtime.
So could something as simple as this work?
1 2 3 |
> pack build demo:0.0.1-SNAPSHOT --builder=paketobuildpacks/builder:base --run-image=debian [...] ERROR: failed to build: invalid run-image 'debian': run-image stack id '' does not match builder stack 'io.buildpacks.stacks.bionic' |
Unfortunately, no…
But it’s not that big of a deal… it’s just a missing label! Let’s build an image with the right label (and proper user
too while we’re at it)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
> cat custom_image/Dockerfile.bionic FROM ubuntu:bionic ARG cnb_uid=1000 ARG cnb_gid=1000 # Install packages that we want to make available at both build and run time RUN apt-get update && \ apt-get install -y xz-utils ca-certificates cowsay # Create user and group RUN groupadd cnb --gid ${cnb_gid} && \ useradd --uid ${cnb_uid} --gid ${cnb_gid} -m -s /bin/bash cnb # Set required CNB information ENV CNB_USER_ID=${cnb_uid} ENV CNB_GROUP_ID=${cnb_gid} # Set required CNB information ARG stack_id LABEL io.buildpacks.stack.id="${stack_id}" # Set user and group (as declared in base image) USER ${CNB_USER_ID}:${CNB_GROUP_ID} > docker build -f custom_image/Dockerfile.bionic --build-arg "stack_id=io.buildpacks.stacks.bionic" -t anthonydahanne/custom-run-image:bionic custom_image/ [...] Successfully tagged anthonydahanne/custom-run-image:bionic > pack build demo:0.0.1-SNAPSHOT --builder=paketobuildpacks/builder:base --run-image=anthonydahanne/custom-run-image:bionic ERROR: failed to build: validating stack mixins: anthonydahanne/custom-run-image:bionic missing required mixin(s): adduser, apt, base-files, base-passwd, bash, bsdutils, bzip2, ca-certificates, coreutils, dash, debconf, debianutils, diffutils, dpkg, e2fsprogs, fdisk, findutils, gcc-8-base, gpgv, grep, gzip, hostname, init-system-helpers, libacl1, libapt-pkg5.0, libattr1, libaudit-common, libaudit1, libblkid1, libbz2-1.0, libc-bin, libc6, libcap-ng0, libcom-err2, libdb5.3, libdebconfclient0, libext2fs2, libfdisk1, libffi6, libgcc1, libgcrypt20, libgmp10, libgnutls30, libgpg-error0, libhogweed4, libidn2-0, liblz4-1, liblzma5, libmount1, libncurses5, libncursesw5, libnettle6, libp11-kit0, libpam-modules, libpam-modules-bin, libpam-runtime, libpam0g, libpcre3, libprocps6, libseccomp2, libselinux1, libsemanage-common, libsemanage1, libsepol1, libsmartcols1, libss2, libssl1.1, libstdc++6, libsystemd0, libtasn1-6, libtinfo5, libudev1, libunistring2, libuuid1, libyaml-0-2, libzstd1, locales, login, lsb-base, mawk, mount, ncurses-base, ncurses-bin, openssl, passwd, perl-base, procps, sed, sensible-utils, sysvinit-utils, tar, tzdata, ubuntu-keyring, util-linux, zlib1g |
Oups, proper mixins
are required by this stack… I could go on and add the required labels, but… maybe I could just extend the existing run image
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
> cat custom_image/Dockerfile.extend FROM paketobuildpacks/run # Install packages that we want to make available USER root RUN apt update && apt install -y cowsay USER cnb > docker build -f custom_image/Dockerfile.extend --build-arg "stack_id=io.buildpacks.stacks.bionic" -t anthonydahanne/custom-run-image:extend custom_image/ [...] Successfully tagged anthonydahanne/custom-run-image:extend > pack build demo:0.0.1-SNAPSHOT --builder=paketobuildpacks/builder:base --run-image=anthonydahanne/custom-run-image:bionic [...] Successfully built image demo:0.0.1-SNAPSHOT > docker run --entrypoint=/usr/games/cowsay -it -p 8080:8080 demo:0.0.1-SNAPSHOT "Hello CNBs\!" _____________ < Hello CNBs! > ------------- \ ^__^ \ (oo)\_______ (__)\ )\/\ ||----w | || || |
How to extend your buildpack builder image
You would extend or replace the buildpack builder image
when you need a special tool or library to build your app; take for example a preprocessor maven
would call via the exec-plugin
Given this scenario, just inherit from the buildpack builder image and add your required libraries / tools
1 2 3 4 5 6 7 8 9 10 11 12 |
> cat custom_image/Dockerfile.extendbuilder FROM paketobuildpacks/builder:base # Install packages that we want to make available USER root RUN apt update && apt install -y cowsay USER cnb > docker build -f custom_image/Dockerfile.extendbuilder -t anthonydahanne/custom-builder-image:extend custom_image [...] Successfully tagged anthonydahanne/custom-builder-image:extend > pack build demo:0.0.1-SNAPSHOT --builder=anthonydahanne/custom-builder-image:extend [...] Successfully built image demo:0.0.1-SNAPSHOT |