Build images with Cloud Native Buildpacks

· December 26, 2020

Cloud Native Buildpacks (CNB) is a specification to transform your application source code into an OCI image. It was initiated by Pivotal and Heroku in 2018, and recently moved from Sandbox to Incubation by the CNCF. If you are interested in how it compares to other popular alternatives like Jib or s2i head over to https://buildpacks.io/features/

A buildpack inspects your application code and transforms the source code into a runnable app image. It has an auto-detection feature, for example a Maven buildpack looks for pom.xml, an NPM buildpack looks for a package.json, etc. There is always a list of ordered buildpacks which are applied on your application source code, which are encapsulated in an image called the builder. The builder contains the ordered list of buildpacks together with the base image to use for the app container image.

The fastest way to get started with CNB is with the pack reference CLI. Pack uses Docker to run the build process in isolated environment.

After installing the pack CLI let’s use it on one example:

$ git clone http://github.com/altfatterz/buildpacks-demo
$ cd buildpacks-demo
$ pack build buildpacks-demo --builder paketobuildpacks/builder:base

By default, the image is uploaded to the local Docker daemon, but with --publish parameter we can specify a registry.

$ docker images

REPOSITORY                 TAG        IMAGE ID       CREATED        SIZE
paketobuildpacks/run       base-cnb   4347963b10a1   5 days ago     106MB
paketobuildpacks/builder   base       03aa716c9552   41 years ago   558MB
buildpacks-demo            latest     a29f2833a7a1   41 years ago   278MB 

We can see that the paketobuildpacks/builder and the run-image paketobuildpacks/run were pulled from the dockerhub. If you are confused by the 41 years ago timestamp it has to do with to allow reproducible builds. Here you can find more information about the details: Time Travel with Pack

Packeto Buildpacks is an implementation of Cloud Native Buildpacks.

With pack we can inspect the built app image. Here we can see that with the used builder which buildpacks were applied to our source code and which was the base image.

$ pack inspect-image buildpacks-demo
...
Base Image:
  Reference: 4347963b10a16e72afa854b12834d41835f4347d7a92f5428bb47c0238464c95
  Top Layer: sha256:8cc74c487427203dd7ff8b5a615b63f9178117a42a4a966f3606fd51746e0fc5

Run Images:
  index.docker.io/paketobuildpacks/run:base-cnb
  gcr.io/paketo-buildpacks/run:base-cnb

Buildpacks:
  ID                                         VERSION
  paketo-buildpacks/ca-certificates          1.0.1
  paketo-buildpacks/bellsoft-liberica        6.0.0
  paketo-buildpacks/maven                    3.2.1
  paketo-buildpacks/executable-jar           3.1.3
  paketo-buildpacks/apache-tomcat            3.1.0
  paketo-buildpacks/dist-zip                 2.2.2
  paketo-buildpacks/spring-boot              3.5.0
... 

Dive is also a great tool to get a view into the file system of each layer.

Dive

We can inspect also the builder using

$ pack inspect-builder paketobuildpacks/builder:base

where we can see that it supports multiple programming languages.

Next to detect and build there are 3 more stages in the lifecycle:

  1. detector - Detects the app type and produces a build plan.
  2. analyzer - Restores layer metadata from the previous image and from the cache.
  3. restorer - Restores cached layers.
  4. builder - Executes buildpacks.
  5. exporter - Creates an image and caches layers.

This ensures that after the first build the builds are much faster, but also safer since only the layers that need to change are replaced.

Spring Boot also has support for the CNB. The Maven and Gradle plugins are using the Packeto Buildpacks implementation.

$ mvn spring-boot:build-image

Below you can explore what is happening in each lifecycle: DETECTING, ANALYZING, RESTORING, BUILDING and EXPORTING. The image is based on the docker.io/paketobuildpacks/run:base-cnb run image, which is a minimal Paketo stack based on Ubuntu 18.04. From the logs we can see that from the 18 available buildpacks 5 are applied to the app source code. The paketo-buildpacks/ca-certificates adds CA certificates to the system truststore at build and runtime, the paketo-buildpacks/bellsoft-liberica requests that a JRE be installed. In the end paketo-buildpacks/spring-boot contributes Spring Boot dependency information and slices an application into multiple layers.

[INFO] Building image 'docker.io/library/buildpacks-demo:0.0.1-SNAPSHOT'
[INFO]
[INFO]  > Pulling builder image 'docker.io/paketobuildpacks/builder:base' 100%
[INFO]  > Pulled builder image 'paketobuildpacks/[email protected]:984a3684db80a6d53214b81a9f21c31529bede5b447d6d6d82d94cd6734d2424'
[INFO]  > Pulling run image 'docker.io/paketobuildpacks/run:base-cnb' 100%
[INFO]  > Pulled run image 'paketobuildpacks/[email protected]:f393fa2927a2619a10fc09bb109f822d20df909c10fed4ce3c36fad313ea18e3'
[INFO]  > Executing lifecycle version v0.10.1
[INFO]  > Using build cache volume 'pack-cache-89b2691a0bb8.build'
[INFO]
[INFO]  > Running creator
[INFO]     [creator]     ===> DETECTING
[INFO]     [creator]     5 of 18 buildpacks participating
[INFO]     [creator]     paketo-buildpacks/ca-certificates   1.0.1
[INFO]     [creator]     paketo-buildpacks/bellsoft-liberica 6.0.0
[INFO]     [creator]     paketo-buildpacks/executable-jar    3.1.3
[INFO]     [creator]     paketo-buildpacks/dist-zip          2.2.2
[INFO]     [creator]     paketo-buildpacks/spring-boot       3.5.0
[INFO]     [creator]     ===> ANALYZING
[INFO]     [creator]     Previous image with name "docker.io/library/buildpacks-demo:0.0.1-SNAPSHOT" not found
[INFO]     [creator]     ===> RESTORING
[INFO]     [creator]     ===> BUILDING
[INFO]     [creator]
[INFO]     [creator]     Paketo CA Certificates Buildpack 1.0.1
[INFO]     [creator]       https://github.com/paketo-buildpacks/ca-certificates
[INFO]     [creator]       Launch Helper: Contributing to layer
[INFO]     [creator]         Creating /layers/paketo-buildpacks_ca-certificates/helper/exec.d/ca-certificates-helper
[INFO]     [creator]         Writing profile.d/helper
[INFO]     [creator]
[INFO]     [creator]     Paketo BellSoft Liberica Buildpack 6.0.0
[INFO]     [creator]       https://github.com/paketo-buildpacks/bellsoft-liberica
[INFO]     [creator]       Build Configuration:
[INFO]     [creator]         $BP_JVM_VERSION              11.*            the Java version
[INFO]     [creator]       Launch Configuration:
[INFO]     [creator]         $BPL_JVM_HEAD_ROOM           0               the headroom in memory calculation
[INFO]     [creator]         $BPL_JVM_LOADED_CLASS_COUNT  35% of classes  the number of loaded classes in memory calculation
[INFO]     [creator]         $BPL_JVM_THREAD_COUNT        250             the number of threads in memory calculation
[INFO]     [creator]         $JAVA_TOOL_OPTIONS                           the JVM launch flags
[INFO]     [creator]       BellSoft Liberica JRE 11.0.9: Contributing to layer
[INFO]     [creator]         Downloading from https://github.com/bell-sw/Liberica/releases/download/11.0.9.1+1/bellsoft-jre11.0.9.1+1-linux-amd64.tar.gz
[INFO]     [creator]         Verifying checksum
[INFO]     [creator]         Expanding to /layers/paketo-buildpacks_bellsoft-liberica/jre
[INFO]     [creator]         Adding 138 container CA certificates to JVM truststore
[INFO]     [creator]         Writing env.launch/BPI_APPLICATION_PATH.default
[INFO]     [creator]         Writing env.launch/BPI_JVM_CACERTS.default
[INFO]     [creator]         Writing env.launch/BPI_JVM_CLASS_COUNT.default
[INFO]     [creator]         Writing env.launch/BPI_JVM_SECURITY_PROVIDERS.default
[INFO]     [creator]         Writing env.launch/JAVA_HOME.default
[INFO]     [creator]         Writing env.launch/MALLOC_ARENA_MAX.default
[INFO]     [creator]       Launch Helper: Contributing to layer
[INFO]     [creator]         Creating /layers/paketo-buildpacks_bellsoft-liberica/helper/exec.d/active-processor-count
[INFO]     [creator]         Creating /layers/paketo-buildpacks_bellsoft-liberica/helper/exec.d/java-opts
[INFO]     [creator]         Creating /layers/paketo-buildpacks_bellsoft-liberica/helper/exec.d/link-local-dns
[INFO]     [creator]         Creating /layers/paketo-buildpacks_bellsoft-liberica/helper/exec.d/memory-calculator
[INFO]     [creator]         Creating /layers/paketo-buildpacks_bellsoft-liberica/helper/exec.d/openssl-certificate-loader
[INFO]     [creator]         Creating /layers/paketo-buildpacks_bellsoft-liberica/helper/exec.d/security-providers-configurer
[INFO]     [creator]         Creating /layers/paketo-buildpacks_bellsoft-liberica/helper/exec.d/security-providers-classpath-9
[INFO]     [creator]         Writing profile.d/helper
[INFO]     [creator]       JVMKill Agent 1.16.0: Contributing to layer
[INFO]     [creator]         Downloading from https://github.com/cloudfoundry/jvmkill/releases/download/v1.16.0.RELEASE/jvmkill-1.16.0-RELEASE.so
[INFO]     [creator]         Verifying checksum
[INFO]     [creator]         Copying to /layers/paketo-buildpacks_bellsoft-liberica/jvmkill
[INFO]     [creator]         Writing env.launch/JAVA_TOOL_OPTIONS.append
[INFO]     [creator]         Writing env.launch/JAVA_TOOL_OPTIONS.delim
[INFO]     [creator]       Java Security Properties: Contributing to layer
[INFO]     [creator]         Writing env.launch/JAVA_SECURITY_PROPERTIES.default
[INFO]     [creator]         Writing env.launch/JAVA_TOOL_OPTIONS.append
[INFO]     [creator]         Writing env.launch/JAVA_TOOL_OPTIONS.delim
[INFO]     [creator]
[INFO]     [creator]     Paketo Executable JAR Buildpack 3.1.3
[INFO]     [creator]       https://github.com/paketo-buildpacks/executable-jar
[INFO]     [creator]         Writing env.launch/CLASSPATH.delim
[INFO]     [creator]         Writing env.launch/CLASSPATH.prepend
[INFO]     [creator]       Process types:
[INFO]     [creator]         executable-jar: java org.springframework.boot.loader.JarLauncher
[INFO]     [creator]         task:           java org.springframework.boot.loader.JarLauncher
[INFO]     [creator]         web:            java org.springframework.boot.loader.JarLauncher
[INFO]     [creator]
[INFO]     [creator]     Paketo Spring Boot Buildpack 3.5.0
[INFO]     [creator]       https://github.com/paketo-buildpacks/spring-boot
[INFO]     [creator]       Creating slices from layers index
[INFO]     [creator]         dependencies
[INFO]     [creator]         spring-boot-loader
[INFO]     [creator]         snapshot-dependencies
[INFO]     [creator]         application
[INFO]     [creator]       Launch Helper: Contributing to layer
[INFO]     [creator]         Creating /layers/paketo-buildpacks_spring-boot/helper/exec.d/spring-cloud-bindings
[INFO]     [creator]         Writing profile.d/helper
[INFO]     [creator]       Web Application Type: Contributing to layer
[INFO]     [creator]         Servlet web application detected
[INFO]     [creator]         Writing env.launch/BPL_JVM_THREAD_COUNT.default
[INFO]     [creator]       Spring Cloud Bindings 1.7.0: Contributing to layer
[INFO]     [creator]         Downloading from https://repo.spring.io/release/org/springframework/cloud/spring-cloud-bindings/1.7.0/spring-cloud-bindings-1.7.0.jar
[INFO]     [creator]         Verifying checksum
[INFO]     [creator]         Copying to /layers/paketo-buildpacks_spring-boot/spring-cloud-bindings
[INFO]     [creator]       4 application slices
[INFO]     [creator]       Image labels:
[INFO]     [creator]         org.opencontainers.image.title
[INFO]     [creator]         org.opencontainers.image.version
[INFO]     [creator]         org.springframework.boot.spring-configuration-metadata.json
[INFO]     [creator]         org.springframework.boot.version
[INFO]     [creator]     ===> EXPORTING
[INFO]     [creator]     Adding layer 'paketo-buildpacks/ca-certificates:helper'
[INFO]     [creator]     Adding layer 'paketo-buildpacks/bellsoft-liberica:helper'
[INFO]     [creator]     Adding layer 'paketo-buildpacks/bellsoft-liberica:java-security-properties'
[INFO]     [creator]     Adding layer 'paketo-buildpacks/bellsoft-liberica:jre'
[INFO]     [creator]     Adding layer 'paketo-buildpacks/bellsoft-liberica:jvmkill'
[INFO]     [creator]     Adding layer 'paketo-buildpacks/executable-jar:class-path'
[INFO]     [creator]     Adding layer 'paketo-buildpacks/spring-boot:helper'
[INFO]     [creator]     Adding layer 'paketo-buildpacks/spring-boot:spring-cloud-bindings'
[INFO]     [creator]     Adding layer 'paketo-buildpacks/spring-boot:web-application-type'
[INFO]     [creator]     Adding 5/5 app layer(s)
[INFO]     [creator]     Adding layer 'launcher'
[INFO]     [creator]     Adding layer 'config'
[INFO]     [creator]     Adding layer 'process-types'
[INFO]     [creator]     Adding label 'io.buildpacks.lifecycle.metadata'
[INFO]     [creator]     Adding label 'io.buildpacks.build.metadata'
[INFO]     [creator]     Adding label 'io.buildpacks.project.metadata'
[INFO]     [creator]     Adding label 'org.opencontainers.image.title'
[INFO]     [creator]     Adding label 'org.opencontainers.image.version'
[INFO]     [creator]     Adding label 'org.springframework.boot.spring-configuration-metadata.json'
[INFO]     [creator]     Adding label 'org.springframework.boot.version'
[INFO]     [creator]     Setting default process type 'web'
[INFO]     [creator]     *** Images (54bf7c7f30b1):
[INFO]     [creator]           docker.io/library/buildpacks-demo:0.0.1-SNAPSHOT
[INFO]
[INFO] Successfully built image 'docker.io/library/buildpacks-demo:0.0.1-SNAPSHOT'
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  29.156 s
[INFO] Finished at: 2020-12-25T19:10:19+01:00
[INFO] ------------------------------------------------------------------------

Now we can run our application using:

$ docker run --rm -p 8080:8080 buildpacks-demo:0.0.1-SNAPSHOT
$ http :8080
Hello, Buildpacker!

With pack is possible to rebase the image to a version pinned run image. This is useful for example when there is a security issue with the run image and with this command we can change only that OS layer in the image.

$ pack rebase buildpacks-demo:latest --run-image paketobuildpacks/run:1.0.11-base-cnb

1.0.11-base-cnb: Pulling from paketobuildpacks/run
Digest: sha256:f393fa2927a2619a10fc09bb109f822d20df909c10fed4ce3c36fad313ea18e3
Status: Image is up to date for paketobuildpacks/run:1.0.11-base-cnb
Rebasing buildpacks-demo:latest on run image paketobuildpacks/run:1.0.11-base-cnb
*** Images (7c8abb9b40d2):
      buildpacks-demo:latest
Rebased Image: 7c8abb9b40d2f50b556efa003642ca5d00a24ec76fb91efc82c52d37887401a5
Successfully rebased image buildpacks-demo:latest

There is another tool kpack which running as a service on Kubernetes could do this rebase on all of your affected images, but that is a topic for another blog post :)

Twitter