Please install the following software.

This document has been confirmed to work with the following versions.

$ java -version              
openjdk version "11.0.10" 2021-01-19 LTS
OpenJDK Runtime Environment (build 11.0.10+9-LTS)
OpenJDK 64-Bit Server VM (build 11.0.10+9-LTS, mixed mode)

$ docker version
Client: Docker Engine - Community
 Cloud integration: 1.0.9
 Version:           20.10.5
 API version:       1.41
 Go version:        go1.13.15
 Git commit:        55c4c88
 Built:             Tue Mar  2 20:13:00 2021
 OS/Arch:           darwin/amd64
 Context:           default
 Experimental:      true

Server: Docker Engine - Community
 Engine:
  Version:          20.10.5
  API version:      1.41 (minimum version 1.12)
  Go version:       go1.13.15
  Git commit:       363e9a8
  Built:            Tue Mar  2 20:15:47 2021
  OS/Arch:          linux/amd64
  Experimental:     true
 containerd:
  Version:          1.4.3
  GitCommit:        269548fa27e0089a8b8278fc4fc781d7f65a939b
 runc:
  Version:          1.0.0-rc92
  GitCommit:        ff819c7e9184c13b7c2607fe6c30ae19403a7aff
 docker-init:
  Version:          0.19.0
  GitCommit:        de40ad0

$ pack version
0.18.0+git-e00ee4a.build-2328


TBD

First of all, let's try Cloud Native Buildpacks. Spring Boot is very easy to get started with Cloud Native Buildpacks available in Maven / Gradle Plugin from 2.3. Create a simple sample app in Spring Boot and create an OCI image using Cloud Native Buildpacks.

Prepare a sample app

Create a Spring Boot template application with Spring Initializr.

Create a hol directory as your working location.

mkdir hol
cd hol


Create a template application with the following command.

curl -s https://start.spring.io/starter.tgz \
 -d artifactId=vehicle-api \
 -d baseDir=vehicle-api \
 -d dependencies=web,jdbc,postgresql,actuator \
 -d packageName=com.example \
 -d applicationName=VehicleApiApplication | tar -xzvf -

If you cannot use the tar command, click this link and click the "GENERATE" button to download the zip file and extract it in the hol directory.

Create src/main/java/com/example/VehicleController.java and write the following contents.

package com.example;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.jdbc.support.KeyHolder;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import java.sql.PreparedStatement;
import java.util.List;

@RestController
public class VehicleController {

  private final JdbcTemplate jdbcTemplate;

  public VehicleController(JdbcTemplate jdbcTemplate) {
     this.jdbcTemplate = jdbcTemplate;
  }

  @GetMapping(path = "/vehicles")
  public ResponseEntity<?> getVehicles() {
     final List<Vehicle> vehicles = this.jdbcTemplate.query("SELECT id, name FROM vehicle ORDER BY id", (rs, i) -> new Vehicle(rs.getInt("id"), rs.getString("name")));
     return ResponseEntity.ok(vehicles);
  }

  @PostMapping(path = "/vehicles")
  public ResponseEntity<?> postVehicles(@RequestBody Vehicle vehicle) {
     final KeyHolder keyHolder = new GeneratedKeyHolder();
     this.jdbcTemplate.update(connection -> {
        final PreparedStatement statement = connection.prepareStatement("INSERT INTO vehicle(name) VALUES (?)", new String[]{"id"});
        statement.setString(1, vehicle.getName());
        return statement;
     }, keyHolder);
     vehicle.setId(keyHolder.getKey().intValue());
     return ResponseEntity.status(HttpStatus.CREATED).body(vehicle);
  }

  @DeleteMapping(path = "/vehicles/{id}")
  public ResponseEntity<?> deleteVehicle(@PathVariable("id") Integer id) {
     this.jdbcTemplate.update("DELETE FROM vehicle WHERE id = ?", id);
     return ResponseEntity.noContent().build();
  }

  static class Vehicle {

     public Vehicle(Integer id, String name) {
        this.id = id;
        this.name = name;
     }

     private Integer id;

     private String name;

     public Integer getId() {
        return id;
     }

     public void setId(Integer id) {
        this.id = id;
     }

     public String getName() {
        return name;
     }

     public void setName(String name) {
        this.name = name;
     }
  }
}


Create src/main/resources/schema.sql and write the following contents.

CREATE TABLE IF NOT EXISTS vehicle
(
   id   SERIAL PRIMARY KEY,
   name VARCHAR(16) UNIQUE
);

Also create src/main/resources/data.sql and describe the following contents.

INSERT INTO vehicle(name) VALUES ('Avalon') ON CONFLICT ON CONSTRAINT vehicle_name_key DO NOTHING;
INSERT INTO vehicle(name) VALUES ('Corolla') ON CONFLICT ON CONSTRAINT vehicle_name_key DO NOTHING;
INSERT INTO vehicle(name) VALUES ('Crown') ON CONFLICT ON CONSTRAINT vehicle_name_key DO NOTHING;
INSERT INTO vehicle(name) VALUES ('Levin') ON CONFLICT ON CONSTRAINT vehicle_name_key DO NOTHING;
INSERT INTO vehicle(name) VALUES ('Yaris') ON CONFLICT ON CONSTRAINT vehicle_name_key DO NOTHING;
INSERT INTO vehicle(name) VALUES ('Vios') ON CONFLICT ON CONSTRAINT vehicle_name_key DO NOTHING;
INSERT INTO vehicle(name) VALUES ('Glanza') ON CONFLICT ON CONSTRAINT vehicle_name_key DO NOTHING;
INSERT INTO vehicle(name) VALUES ('Aygo') ON CONFLICT ON CONSTRAINT vehicle_name_key DO NOTHING;


Next, write the following contents in src/main/resources/application.properties.

spring.datasource.driver-class-name=org.postgresql.Driver
spring.datasource.url=jdbc:postgresql://localhost:5432/vehicle
spring.datasource.username=vehicle
spring.datasource.password=vehicle
spring.datasource.initialization-mode=always


Build the application with the following command.

cd vehicle-api
./mvnw clean package -Dmaven.test.skip=true

Start PostgreSQL with Docker to check the operation of the application locally. Open a new terminal and run the following command.

docker run --rm \
 -p 5432:5432 \
 -e POSTGRES_DB=vehicle \
 -e POSTGRES_USER=vehicle \
 -e POSTGRES_PASSWORD=vehicle \
 bitnami/postgresql:11.11.0-debian-10-r59

Launch the application.

$ java -jar target/vehicle-api-0.0.1-SNAPSHOT.jar
  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v2.4.4)

2021-04-09 15:14:09.018  INFO 81564 --- [           main] com.example.VehicleApiApplication        : Starting VehicleApiApplication v0.0.1-SNAPSHOT using Java 11.0.10 on makinoMacBook-Pro.local with PID 81564 (/Users/toshiaki/hol/vehicle-api/target/vehicle-api-0.0.1-SNAPSHOT.jar started by toshiaki in /Users/toshiaki/hol/vehicle-api)
...

Access the launched application.

$ curl -s http://localhost:8080/vehicles | jq .
[
  {
    "id": 1,
    "name": "Avalon"
  },
  {
    "id": 2,
    "name": "Corolla"
  },
  {
    "id": 3,
    "name": "Crown"
  },
  {
    "id": 4,
    "name": "Levin"
  },
  {
    "id": 5,
    "name": "Yaris"
  },
  {
    "id": 6,
    "name": "Vios"
  },
  {
    "id": 7,
    "name": "Glanza"
  },
  {
    "id": 8,
    "name": "Aygo"
  }
]

$ curl -s http://localhost:8080/vehicles -d "{\"name\": \"Lexus\"}" -H "Content-Type: application/json" | jq .
{
  "id": 9,
  "name": "Lexus"
}

$ curl -s http://localhost:8080/vehicles | jq .
[
  {
    "id": 1,
    "name": "Avalon"
  },
  {
    "id": 2,
    "name": "Corolla"
  },
  {
    "id": 3,
    "name": "Crown"
  },
  {
    "id": 4,
    "name": "Levin"
  },
  {
    "id": 5,
    "name": "Yaris"
  },
  {
    "id": 6,
    "name": "Vios"
  },
  {
    "id": 7,
    "name": "Glanza"
  },
  {
    "id": 8,
    "name": "Aygo"
  },
  {
    "id": 9,
    "name": "Lexus"
  }
]


At this point the application is ready. Stop the application with Ctrl + C.

Build with Spring Boot Maven Plugin

Use Spring Boot Maven Plugin to create an OCI image of your application. Execute the following command.

./mvnw spring-boot:build-image -Dmaven.test.skip=true

The following log will be output.

[INFO] Scanning for projects...
[INFO] 
[INFO] ----------------------< com.example:vehicle-api >-----------------------
[INFO] Building demo 0.0.1-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO] 
...
[INFO] 
[INFO] <<< spring-boot-maven-plugin:2.4.4:build-image (default-cli) < package @ vehicle-api <<<
[INFO] 
[INFO] 
[INFO] --- spring-boot-maven-plugin:2.4.4:build-image (default-cli) @ vehicle-api ---
[INFO] Building image 'docker.io/library/vehicle-api:0.0.1-SNAPSHOT'
[INFO] 
[INFO]  > Pulling builder image 'docker.io/paketobuildpacks/builder:base' 100%
[INFO]  > Pulled builder image 'paketobuildpacks/builder@sha256:25fc8c5ab1fb229415e58cab0bd1bb1fa8a6b96ba40867cb8e94541ed618de1e'
[INFO]  > Pulling run image 'docker.io/paketobuildpacks/run:base-cnb' 100%
[INFO]  > Pulled run image 'paketobuildpacks/run@sha256:6c5acd07ae6820c606bb4aea6dd76c7db5bafa53b5b3b3f938267779966f8daf'
[INFO]  > Executing lifecycle version v0.11.1
[INFO]  > Using build cache volume 'pack-cache-13a8f10fbd06.build'
[INFO] 
[INFO]  > Running creator
[INFO]     [creator]     ===> DETECTING
[INFO]     [creator]     5 of 18 buildpacks participating
[INFO]     [creator]     paketo-buildpacks/ca-certificates   2.1.0
[INFO]     [creator]     paketo-buildpacks/bellsoft-liberica 7.1.0
[INFO]     [creator]     paketo-buildpacks/executable-jar    5.0.0
[INFO]     [creator]     paketo-buildpacks/dist-zip          4.0.0
[INFO]     [creator]     paketo-buildpacks/spring-boot       4.2.0
[INFO]     [creator]     ===> ANALYZING
[INFO]     [creator]     Previous image with name "docker.io/library/vehicle-api:0.0.1-SNAPSHOT" not found
[INFO]     [creator]     ===> RESTORING
[INFO]     [creator]     ===> BUILDING
[INFO]     [creator]     
[INFO]     [creator]     Paketo CA Certificates Buildpack 2.1.0
[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]     
[INFO]     [creator]     Paketo BellSoft Liberica Buildpack 7.1.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.10: Contributing to layer
[INFO]     [creator]         Downloading from https://github.com/bell-sw/Liberica/releases/download/11.0.10+9/bellsoft-jre11.0.10+9-linux-amd64.tar.gz
[INFO]     [creator]         Verifying checksum
[INFO]     [creator]         Expanding to /layers/paketo-buildpacks_bellsoft-liberica/jre
[INFO]     [creator]         Adding 129 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]       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 5.0.0
[INFO]     [creator]       https://github.com/paketo-buildpacks/executable-jar
[INFO]     [creator]       Class Path: Contributing to layer
[INFO]     [creator]         Writing env/CLASSPATH.delim
[INFO]     [creator]         Writing env/CLASSPATH.prepend
[INFO]     [creator]       Process types:
[INFO]     [creator]         executable-jar: java org.springframework.boot.loader.JarLauncher (direct)
[INFO]     [creator]         task:           java org.springframework.boot.loader.JarLauncher (direct)
[INFO]     [creator]         web:            java org.springframework.boot.loader.JarLauncher (direct)
[INFO]     [creator]     
[INFO]     [creator]     Paketo Spring Boot Buildpack 4.2.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]       Spring Cloud Bindings 1.7.1: Contributing to layer
[INFO]     [creator]         Downloading from https://repo.spring.io/release/org/springframework/cloud/spring-cloud-bindings/1.7.1/spring-cloud-bindings-1.7.1.jar
[INFO]     [creator]         Verifying checksum
[INFO]     [creator]         Copying to /layers/paketo-buildpacks_spring-boot/spring-cloud-bindings
[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]       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:classpath'
[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]     Saving docker.io/library/vehicle-api:0.0.1-SNAPSHOT...
[INFO]     [creator]     *** Images (c0cbc7fd51b0):
[INFO]     [creator]           docker.io/library/vehicle-api:0.0.1-SNAPSHOT
[INFO] 
[INFO] Successfully built image 'docker.io/library/vehicle-api:0.0.1-SNAPSHOT'
[INFO] 
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  57.056 s
[INFO] Finished at: 2021-04-09T15:21:50+09:00
[INFO] ------------------------------------------------------------------------

An OCI image named docker.io/library/vehicle-api:0.0.1-SNAPSHOT has been created.

Check the image list with the following command.

$ docker images
REPOSITORY                 TAG                     IMAGE ID       CREATED        SIZE
bitnami/postgresql         11.11.0-debian-10-r59   c416eed2ffd6   17 hours ago   258MB
paketobuildpacks/run       base-cnb                8e1ab607b638   6 days ago     87.2MB
vehicle-api                0.0.1-SNAPSHOT          c0cbc7fd51b0   41 years ago   262MB
paketobuildpacks/builder   base                    c8f7e51dbb67   41 years ago   660MB

Run the created image. Execute the following command.

docker run --rm \
 -p 8080:8080 \
 -m 1g \
 -e SPRING_DATASOURCE_URL=jdbc:postgresql://host.docker.internal:5432/vehicle \
 docker.io/library/vehicle-api:0.0.1-SNAPSHOT

The following log is output.

Setting Active Processor Count to 4
Calculated JVM Memory Configuration: -XX:MaxDirectMemorySize=10M -Xmx449112K -XX:MaxMetaspaceSize=87463K -XX:ReservedCodeCacheSize=240M -Xss1M (Total Memory: 1G, Thread Count: 250, Loaded Class Count: 13028, Headroom: 0%)
Adding 129 container CA certificates to JVM truststore
Spring Cloud Bindings Enabled
Picked up JAVA_TOOL_OPTIONS: -Djava.security.properties=/layers/paketo-buildpacks_bellsoft-liberica/java-security-properties/java-security.properties -agentpath:/layers/paketo-buildpacks_bellsoft-liberica/jvmkill/jvmkill-1.16.0-RELEASE.so=printHeapHistogram=1 -XX:ActiveProcessorCount=4 -XX:MaxDirectMemorySize=10M -Xmx449112K -XX:MaxMetaspaceSize=87463K -XX:ReservedCodeCacheSize=240M -Xss1M -Dorg.springframework.cloud.bindings.boot.enable=true

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v2.4.4)

2021-04-09 06:41:03.173  INFO 1 --- [           main] com.example.VehicleApiApplication        : Starting VehicleApiApplication v0.0.1-SNAPSHOT using Java 11.0.10 on ab785b1ccd44 with PID 1 (/workspace/BOOT-INF/classes started by cnb in /workspace)
2021-04-09 06:41:03.177  INFO 1 --- [           main] com.example.VehicleApiApplication        : No active profile set, falling back to default profiles: default
2021-04-09 06:41:04.623  INFO 1 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2021-04-09 06:41:04.636  INFO 1 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2021-04-09 06:41:04.636  INFO 1 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.44]
2021-04-09 06:41:04.696  INFO 1 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2021-04-09 06:41:04.697  INFO 1 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1454 ms
2021-04-09 06:41:04.997  INFO 1 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
2021-04-09 06:41:05.128  INFO 1 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
2021-04-09 06:41:05.330  INFO 1 --- [           main] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'
2021-04-09 06:41:05.646  INFO 1 --- [           main] o.s.b.a.e.web.EndpointLinksResolver      : Exposing 2 endpoint(s) beneath base path '/actuator'
2021-04-09 06:41:05.685  INFO 1 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2021-04-09 06:41:05.701  INFO 1 --- [           main] com.example.VehicleApiApplication        : Started VehicleApiApplication in 3.083 seconds (JVM running for 3.565)

Access the application with curl.

$ curl -s http://localhost:8080/vehicles | jq .
[
  {
    "id": 1,
    "name": "Avalon"
  },
  {
    "id": 2,
    "name": "Corolla"
  },
  {
    "id": 3,
    "name": "Crown"
  },
  {
    "id": 4,
    "name": "Levin"
  },
  {
    "id": 5,
    "name": "Yaris"
  },
  {
    "id": 6,
    "name": "Vios"
  },
  {
    "id": 7,
    "name": "Glanza"
  },
  {
    "id": 8,
    "name": "Aygo"
  },
  {
    "id": 9,
    "name": "Lexus"
  }
]

An OCI image of your Spring Boot application was created without creating a Dockerfile.

Build with pack CLI

Next, create an OCI image of your application created using the pack CLI.

from an executable jar file

First, create an OCI image from a pre-built executable jar file. Run the following command.

pack build vehicle-api \
 --path ./target/vehicle-api-0.0.1-SNAPSHOT.jar \
 --builder paketobuildpacks/builder:base

The following log is output.

base: Pulling from paketobuildpacks/builder
Digest: sha256:25fc8c5ab1fb229415e58cab0bd1bb1fa8a6b96ba40867cb8e94541ed618de1e
Status: Image is up to date for paketobuildpacks/builder:base
base-cnb: Pulling from paketobuildpacks/run
Digest: sha256:6c5acd07ae6820c606bb4aea6dd76c7db5bafa53b5b3b3f938267779966f8daf
Status: Image is up to date for paketobuildpacks/run:base-cnb
===> DETECTING
5 of 18 buildpacks participating
paketo-buildpacks/ca-certificates   2.1.0
paketo-buildpacks/bellsoft-liberica 7.1.0
paketo-buildpacks/executable-jar    5.0.0
paketo-buildpacks/dist-zip          4.0.0
paketo-buildpacks/spring-boot       4.2.0
===> ANALYZING
Previous image with name "vehicle-api" not found
===> RESTORING
===> BUILDING

Paketo CA Certificates Buildpack 2.1.0
  https://github.com/paketo-buildpacks/ca-certificates
  Launch Helper: Contributing to layer
    Creating /layers/paketo-buildpacks_ca-certificates/helper/exec.d/ca-certificates-helper

Paketo BellSoft Liberica Buildpack 7.1.0
  https://github.com/paketo-buildpacks/bellsoft-liberica
  Build Configuration:
    $BP_JVM_VERSION              11              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 JRE 11.0.10: Contributing to layer
    Downloading from https://github.com/bell-sw/Liberica/releases/download/11.0.10+9/bellsoft-jre11.0.10+9-linux-amd64.tar.gz
    Verifying checksum
    Expanding to /layers/paketo-buildpacks_bellsoft-liberica/jre
    Adding 129 container CA certificates to JVM truststore
    Writing env.launch/BPI_APPLICATION_PATH.default
    Writing env.launch/BPI_JVM_CACERTS.default
    Writing env.launch/BPI_JVM_CLASS_COUNT.default
    Writing env.launch/BPI_JVM_SECURITY_PROVIDERS.default
    Writing env.launch/JAVA_HOME.default
    Writing env.launch/MALLOC_ARENA_MAX.default
  Launch Helper: Contributing to layer
    Creating /layers/paketo-buildpacks_bellsoft-liberica/helper/exec.d/active-processor-count
    Creating /layers/paketo-buildpacks_bellsoft-liberica/helper/exec.d/java-opts
    Creating /layers/paketo-buildpacks_bellsoft-liberica/helper/exec.d/link-local-dns
    Creating /layers/paketo-buildpacks_bellsoft-liberica/helper/exec.d/memory-calculator
    Creating /layers/paketo-buildpacks_bellsoft-liberica/helper/exec.d/openssl-certificate-loader
    Creating /layers/paketo-buildpacks_bellsoft-liberica/helper/exec.d/security-providers-configurer
    Creating /layers/paketo-buildpacks_bellsoft-liberica/helper/exec.d/security-providers-classpath-9
  JVMKill Agent 1.16.0: Contributing to layer
    Downloading from https://github.com/cloudfoundry/jvmkill/releases/download/v1.16.0.RELEASE/jvmkill-1.16.0-RELEASE.so
    Verifying checksum
    Copying to /layers/paketo-buildpacks_bellsoft-liberica/jvmkill
    Writing env.launch/JAVA_TOOL_OPTIONS.append
    Writing env.launch/JAVA_TOOL_OPTIONS.delim
  Java Security Properties: Contributing to layer
    Writing env.launch/JAVA_SECURITY_PROPERTIES.default
    Writing env.launch/JAVA_TOOL_OPTIONS.append
    Writing env.launch/JAVA_TOOL_OPTIONS.delim

Paketo Executable JAR Buildpack 5.0.0
  https://github.com/paketo-buildpacks/executable-jar
  Class Path: Contributing to layer
    Writing env/CLASSPATH.delim
    Writing env/CLASSPATH.prepend
  Process types:
    executable-jar: java org.springframework.boot.loader.JarLauncher (direct)
    task:           java org.springframework.boot.loader.JarLauncher (direct)
    web:            java org.springframework.boot.loader.JarLauncher (direct)

Paketo Spring Boot Buildpack 4.2.0
  https://github.com/paketo-buildpacks/spring-boot
  Creating slices from layers index
    dependencies
    spring-boot-loader
    snapshot-dependencies
    application
  Launch Helper: Contributing to layer
    Creating /layers/paketo-buildpacks_spring-boot/helper/exec.d/spring-cloud-bindings
  Spring Cloud Bindings 1.7.1: Contributing to layer
    Downloading from https://repo.spring.io/release/org/springframework/cloud/spring-cloud-bindings/1.7.1/spring-cloud-bindings-1.7.1.jar
    Verifying checksum
    Copying to /layers/paketo-buildpacks_spring-boot/spring-cloud-bindings
  Web Application Type: Contributing to layer
    Servlet web application detected
    Writing env.launch/BPL_JVM_THREAD_COUNT.default
  4 application slices
  Image labels:
    org.opencontainers.image.title
    org.opencontainers.image.version
    org.springframework.boot.spring-configuration-metadata.json
    org.springframework.boot.version
===> EXPORTING
Adding layer 'paketo-buildpacks/ca-certificates:helper'
Adding layer 'paketo-buildpacks/bellsoft-liberica:helper'
Adding layer 'paketo-buildpacks/bellsoft-liberica:java-security-properties'
Adding layer 'paketo-buildpacks/bellsoft-liberica:jre'
Adding layer 'paketo-buildpacks/bellsoft-liberica:jvmkill'
Adding layer 'paketo-buildpacks/executable-jar:classpath'
Adding layer 'paketo-buildpacks/spring-boot:helper'
Adding layer 'paketo-buildpacks/spring-boot:spring-cloud-bindings'
Adding layer 'paketo-buildpacks/spring-boot:web-application-type'
Adding 5/5 app layer(s)
Adding layer 'launcher'
Adding layer 'config'
Adding layer 'process-types'
Adding label 'io.buildpacks.lifecycle.metadata'
Adding label 'io.buildpacks.build.metadata'
Adding label 'io.buildpacks.project.metadata'
Adding label 'org.opencontainers.image.title'
Adding label 'org.opencontainers.image.version'
Adding label 'org.springframework.boot.spring-configuration-metadata.json'
Adding label 'org.springframework.boot.version'
Setting default process type 'web'
Saving vehicle-api...
*** Images (c0cbc7fd51b0):
      vehicle-api
Successfully built image vehicle-api

An image named vehicle-api has been created. Run the following command as before to check the operation.

docker run --rm \
 -p 8080:8080 \
 -m 1g \
 -e SPRING_DATASOURCE_URL=jdbc:postgresql://host.docker.internal:5432/vehicle \
 vehicle-api

The image created is the same as the one created with the Maven Plugin. Choose the one that is easy to use whether to use the pack CLI or Maven Plugin.

from source code

Unlike the Maven Plugin which creates an OCI image only from a jar file, if you use the pack CLI You can create an OCI image from any of bellow

This time, create the image directly from the source code.

In this case, Maven build is also done on the container. Run the following command.

pack build vehicle-api \
 --builder paketobuildpacks/builder:base

The following log is output.

base: Pulling from paketobuildpacks/builder
Digest: sha256:25fc8c5ab1fb229415e58cab0bd1bb1fa8a6b96ba40867cb8e94541ed618de1e
Status: Image is up to date for paketobuildpacks/builder:base
base-cnb: Pulling from paketobuildpacks/run
Digest: sha256:6c5acd07ae6820c606bb4aea6dd76c7db5bafa53b5b3b3f938267779966f8daf
Status: Image is up to date for paketobuildpacks/run:base-cnb
===> DETECTING
7 of 18 buildpacks participating
paketo-buildpacks/ca-certificates   2.1.0
paketo-buildpacks/bellsoft-liberica 7.1.0
paketo-buildpacks/maven             5.0.0
paketo-buildpacks/executable-jar    5.0.0
paketo-buildpacks/apache-tomcat     5.1.0
paketo-buildpacks/dist-zip          4.0.0
paketo-buildpacks/spring-boot       4.2.0
===> ANALYZING
Restoring metadata for "paketo-buildpacks/ca-certificates:helper" from app image
Restoring metadata for "paketo-buildpacks/bellsoft-liberica:helper" from app image
Restoring metadata for "paketo-buildpacks/bellsoft-liberica:java-security-properties" from app image
Restoring metadata for "paketo-buildpacks/bellsoft-liberica:jre" from app image
Restoring metadata for "paketo-buildpacks/bellsoft-liberica:jvmkill" from app image
Restoring metadata for "paketo-buildpacks/spring-boot:helper" from app image
Restoring metadata for "paketo-buildpacks/spring-boot:spring-cloud-bindings" from app image
Restoring metadata for "paketo-buildpacks/spring-boot:web-application-type" from app image
===> RESTORING
===> BUILDING

Paketo CA Certificates Buildpack 2.1.0
  https://github.com/paketo-buildpacks/ca-certificates
  Launch Helper: Reusing cached layer

Paketo BellSoft Liberica Buildpack 7.1.0
  https://github.com/paketo-buildpacks/bellsoft-liberica
  Build Configuration:
    $BP_JVM_VERSION              11              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 11.0.10: Contributing to layer
    Downloading from https://github.com/bell-sw/Liberica/releases/download/11.0.10+9/bellsoft-jdk11.0.10+9-linux-amd64.tar.gz
    Verifying checksum
    Expanding to /layers/paketo-buildpacks_bellsoft-liberica/jdk
    Adding 129 container CA certificates to JVM truststore
    Writing env.build/JAVA_HOME.override
    Writing env.build/JDK_HOME.override
  BellSoft Liberica JRE 11.0.10: Reusing cached layer
  Launch Helper: Reusing cached layer
  JVMKill Agent 1.16.0: Reusing cached layer
  Java Security Properties: Reusing cached layer

Paketo Maven Buildpack 5.0.0
  https://github.com/paketo-buildpacks/maven
  Build Configuration:
    $BP_MAVEN_BUILD_ARGUMENTS  -Dmaven.test.skip=true package  the arguments to pass to Maven
    $BP_MAVEN_BUILT_ARTIFACT   target/*.[jw]ar                 the built application artifact explicitly.  Supersedes $BP_MAVEN_BUILT_MODULE
    $BP_MAVEN_BUILT_MODULE                                     the module to find application artifact in
    Creating cache directory /home/cnb/.m2
  Compiled Application: Contributing to layer
    Executing mvnw --batch-mode -Dmaven.test.skip=true package
[INFO] Scanning for projects...
[INFO] Downloading from central: https://repo.maven.apache.org/maven2/org/springframework/boot/spring-boot-starter-parent/2.4.4/spring-boot-starter-parent-2.4.4.pom
...
[INFO] Downloaded from central: https://repo.maven.apache.org/maven2/net/java/dev/jna/jna/5.5.0/jna-5.5.0.jar (1.5 MB at 398 kB/s)
[INFO] Replacing main artifact with repackaged archive
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  02:28 min
[INFO] Finished at: 2021-04-09T07:12:25Z
[INFO] ------------------------------------------------------------------------
  Removing source code

Paketo Executable JAR Buildpack 5.0.0
  https://github.com/paketo-buildpacks/executable-jar
  Class Path: Contributing to layer
    Writing env/CLASSPATH.delim
    Writing env/CLASSPATH.prepend
  Process types:
    executable-jar: java org.springframework.boot.loader.JarLauncher (direct)
    task:           java org.springframework.boot.loader.JarLauncher (direct)
    web:            java org.springframework.boot.loader.JarLauncher (direct)

Paketo Spring Boot Buildpack 4.2.0
  https://github.com/paketo-buildpacks/spring-boot
  Creating slices from layers index
    dependencies
    spring-boot-loader
    snapshot-dependencies
    application
  Launch Helper: Reusing cached layer
  Spring Cloud Bindings 1.7.1: Reusing cached layer
  Web Application Type: Reusing cached layer
  4 application slices
  Image labels:
    org.opencontainers.image.title
    org.opencontainers.image.version
    org.springframework.boot.spring-configuration-metadata.json
    org.springframework.boot.version
===> EXPORTING
Reusing layer 'paketo-buildpacks/ca-certificates:helper'
Reusing layer 'paketo-buildpacks/bellsoft-liberica:helper'
Reusing layer 'paketo-buildpacks/bellsoft-liberica:java-security-properties'
Reusing layer 'paketo-buildpacks/bellsoft-liberica:jre'
Reusing layer 'paketo-buildpacks/bellsoft-liberica:jvmkill'
Reusing layer 'paketo-buildpacks/executable-jar:classpath'
Reusing layer 'paketo-buildpacks/spring-boot:helper'
Reusing layer 'paketo-buildpacks/spring-boot:spring-cloud-bindings'
Reusing layer 'paketo-buildpacks/spring-boot:web-application-type'
Reusing 5/5 app layer(s)
Reusing layer 'launcher'
Adding layer 'config'
Reusing layer 'process-types'
Adding label 'io.buildpacks.lifecycle.metadata'
Adding label 'io.buildpacks.build.metadata'
Adding label 'io.buildpacks.project.metadata'
Adding label 'org.opencontainers.image.title'
Adding label 'org.opencontainers.image.version'
Adding label 'org.springframework.boot.spring-configuration-metadata.json'
Adding label 'org.springframework.boot.version'
Setting default process type 'web'
Saving vehicle-api...
*** Images (bd392fae2c31):
      vehicle-api
Adding cache layer 'paketo-buildpacks/bellsoft-liberica:jdk'
Adding cache layer 'paketo-buildpacks/maven:application'
Adding cache layer 'paketo-buildpacks/maven:cache'
Successfully built image vehicle-api

Maven built the application after running the pack build command. The first time there is no cache, so it took a long time, but after the second build, the cache is used, so it will be faster.

Run the following command as before to check the operation.

docker run --rm \
 -p 8080:8080 \
 -m 1g \
 -e SPRING_DATASOURCE_URL=jdbc:postgresql://host.docker.internal:5432/vehicle \
 vehicle-api

Introducing the Cloud Native Buildpacks Ecosystem.

Builder

When creating a container image with Cloud Native Buildpacks, first you will select a Builder. A Builder consists of the following components.

The resulting image will differ depending on the builder used, and the supported languages will also differ.

A typical builder can be confirmed with pack builder suggest command as follows.

$ pack builder suggest
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              Base builder for Heroku-18 stack, based on ubuntu:18.04 base image                                                        
        Heroku:                heroku/buildpacks:20              Base builder for Heroku-20 stack, based on ubuntu:20.04 base image                                                        
        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              

Tip: Learn more about a specific builder with:
        pack builder inspect <builder-image>

This hands-on basically uses paketobuildpacks/builder:base. This Builder was also used when creating an image with the Spring Boot Maven Plugin.

Platform

In Cloud Native Buildpacks terminology, Platform is an application or system that includes the ability to create container images using Lifecycle and Buildpack. The pack CLI and Spring Boot Maven Plugin used so far are also one of the Platforms. There are already various platforms as bellow.

Local Build

CI

Application Platform

You can see information such as which Buildpack the image was created with with the pack inspect command.

$ pack inspect docker.io/library/vehicle-api:0.0.1-SNAPSHOT               
Inspecting image: docker.io/library/vehicle-api:0.0.1-SNAPSHOT

REMOTE:
(not present)

LOCAL:

Stack: io.buildpacks.stacks.bionic

Base Image:
  Reference: 8e1ab607b6386d5d9455047383dccecc89cb4ecd8891f18e6aa35e7a686db162
  Top Layer: sha256:10aba31d775e5770b8dc5fc9a28047f71245d8bfdd7e5b7c15035f95ba46b8c5

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

Buildpacks:
  ID                                         VERSION        HOMEPAGE
  paketo-buildpacks/ca-certificates          2.1.0          https://github.com/paketo-buildpacks/ca-certificates
  paketo-buildpacks/bellsoft-liberica        7.1.0          https://github.com/paketo-buildpacks/bellsoft-liberica
  paketo-buildpacks/executable-jar           5.0.0          https://github.com/paketo-buildpacks/executable-jar
  paketo-buildpacks/dist-zip                 4.0.0          https://github.com/paketo-buildpacks/dist-zip
  paketo-buildpacks/spring-boot              4.2.0          https://github.com/paketo-buildpacks/spring-boot

Processes:
  TYPE                  SHELL        COMMAND        ARGS
  web (default)                      java           org.springframework.boot.loader.JarLauncher
  executable-jar                     java           org.springframework.boot.loader.JarLauncher
  task                               java           org.springframework.boot.loader.JarLauncher

You can see that the ID of the Run Image is 8e1ab607b6386d5d9455047383dccecc89cb4ecd8891f18e6aa35e7a686db162. You can see that this ID matches that of paketobuildpacks/run:base-cnb from the following command.

$ docker images --no-trunc | grep 8e1ab607b6386d5d9455047383dccecc89cb4ecd8891f18e6aa35e7a686db162
paketobuildpacks/run       base-cnb                sha256:8e1ab607b6386d5d9455047383dccecc89cb4ecd8891f18e6aa35e7a686db162   6 days ago       87.2MB

With the --bom option, you can also get the binary version used by the image and a list of dependent libraries.

$ pack inspect docker.io/library/vehicle-api:0.0.1-SNAPSHOT --bom
{
  "remote": null,
  "local": [
    {
      "name": "helper",
      "metadata": {
        "layer": "helper",
        "names": [
          "ca-certificates-helper"
        ],
        "version": "2.1.0"
      },
      "buildpacks": {
        "id": "paketo-buildpacks/ca-certificates",
        "version": "2.1.0"
      }
    },
    {
      "name": "jre",
      "metadata": {
        "layer": "jre",
        "licenses": [
          {
            "type": "GPL-2.0 WITH Classpath-exception-2.0",
            "uri": "https://openjdk.java.net/legal/gplv2+ce.html"
          }
        ],
        "name": "BellSoft Liberica JRE",
        "sha256": "2ca618d3122535aa39e713dce18286622a410ab2f003d73b321520f28aad0c36",
        "stacks": [
          "io.buildpacks.stacks.bionic",
          "org.cloudfoundry.stacks.cflinuxfs3"
        ],
        "uri": "https://github.com/bell-sw/Liberica/releases/download/11.0.10+9/bellsoft-jre11.0.10+9-linux-amd64.tar.gz",
        "version": "11.0.10"
      },
      "buildpacks": {
        "id": "paketo-buildpacks/bellsoft-liberica",
        "version": "7.1.0"
      }
    },
    {
      "name": "helper",
      "metadata": {
        "layer": "helper",
        "names": [
          "active-processor-count",
          "java-opts",
          "link-local-dns",
          "memory-calculator",
          "openssl-certificate-loader",
          "security-providers-configurer",
          "security-providers-classpath-9"
        ],
        "version": "7.1.0"
      },
      "buildpacks": {
        "id": "paketo-buildpacks/bellsoft-liberica",
        "version": "7.1.0"
      }
    },
    {
      "name": "jvmkill",
      "metadata": {
        "layer": "jvmkill",
        "licenses": [
          {
            "type": "Apache-2.0",
            "uri": "https://github.com/cloudfoundry/jvmkill/blob/main/LICENSE"
          }
        ],
        "name": "JVMKill Agent",
        "sha256": "a3092627b082cb3cdbbe4b255d35687126aa604e6b613dcda33be9f7e1277162",
        "stacks": [
          "io.buildpacks.stacks.bionic",
          "org.cloudfoundry.stacks.cflinuxfs3"
        ],
        "uri": "https://github.com/cloudfoundry/jvmkill/releases/download/v1.16.0.RELEASE/jvmkill-1.16.0-RELEASE.so",
        "version": "1.16.0"
      },
      "buildpacks": {
        "id": "paketo-buildpacks/bellsoft-liberica",
        "version": "7.1.0"
      }
    },
    {
      "name": "dependencies",
      "metadata": {
        "dependencies": [
          {
            "name": "HdrHistogram",
            "sha256": "9b47fbae444feaac4b7e04f0ea294569e4bc282bc69d8c2ce2ac3f23577281e2",
            "version": "2.1.12"
          },
          {
            "name": "HikariCP",
            "sha256": "8b732f9470570d4a841dc1ef6c826b586978b25ba830712ff1fa59de275dfa61",
            "version": "3.4.5"
          },
          {
            "name": "LatencyUtils",
            "sha256": "a32a9ffa06b2f4e01c5360f8f9df7bc5d9454a5d373cd8f361347fa5a57165ec",
            "version": "2.0.3"
          },
          {
            "name": "checker-qual",
            "sha256": "729990b3f18a95606fc2573836b6958bcdb44cb52bfbd1b7aa9c339cff35a5a4",
            "version": "3.5.0"
          },
          {
            "name": "jackson-annotations",
            "sha256": "2ab76f64048673675f26ddd1008a32889855d8a126273edae2aeee516785a1ec",
            "version": "2.11.4"
          },
          {
            "name": "jackson-core",
            "sha256": "e1dda269f16f3be00578f3a46c754f098245c7a480b86e07030b6ee7087ee1f1",
            "version": "2.11.4"
          },
          {
            "name": "jackson-databind",
            "sha256": "dc64fa3907bd299f29ad6116169e583333d04404b23a0f81ed679afa8e2a2ee8",
            "version": "2.11.4"
          },
          {
            "name": "jackson-datatype-jdk8",
            "sha256": "2ed8e417d190c370753ed3eb4f5193378e11ddea8736f952bd0264844600faaf",
            "version": "2.11.4"
          },
          {
            "name": "jackson-datatype-jsr310",
            "sha256": "4f70ec64696f8de547773d7f8d5243ab7d11dc42a854df1764fa16837b6f53af",
            "version": "2.11.4"
          },
          {
            "name": "jackson-module-parameter-names",
            "sha256": "fb3f84a7318af5a7b577ecf23d577d6b0e2162496dd61ab3f2a39df1a1c1cc3e",
            "version": "2.11.4"
          },
          {
            "name": "jakarta.annotation-api",
            "sha256": "85fb03fc054cdf4efca8efd9b6712bbb418e1ab98241c4539c8585bbc23e1b8a",
            "version": "1.3.5"
          },
          {
            "name": "jakarta.el",
            "sha256": "e2bcb8551b02a5c2afdc4cab77302ba5c76705cf1fc832345ca880df80bf4716",
            "version": "3.0.3"
          },
          {
            "name": "jul-to-slf4j",
            "sha256": "bbcbfdaa72572255c4f85207a9bfdb24358dc993e41252331bd4d0913e4988b9",
            "version": "1.7.30"
          },
          {
            "name": "log4j-api",
            "sha256": "2b4b1965c9dce7f3732a0fbf5c8493199c1e6bf8cf65c3e235b57d98da5f36af",
            "version": "2.13.3"
          },
          {
            "name": "log4j-to-slf4j",
            "sha256": "9624e9aaf60b1875adde33d8e7997de110b70be09e94e55ad8fc39637ec002c4",
            "version": "2.13.3"
          },
          {
            "name": "logback-classic",
            "sha256": "fb53f8539e7fcb8f093a56e138112056ec1dc809ebb020b59d8a36a5ebac37e0",
            "version": "1.2.3"
          },
          {
            "name": "logback-core",
            "sha256": "5946d837fe6f960c02a53eda7a6926ecc3c758bbdd69aa453ee429f858217f22",
            "version": "1.2.3"
          },
          {
            "name": "micrometer-core",
            "sha256": "766e5ac5f3ae1e07738ff3ce5a8b818399d6b05250179d3d4b5b93ad027d59e6",
            "version": "1.6.5"
          },
          {
            "name": "postgresql",
            "sha256": "232747fa0924d88a26dfa42b8128b6fa1140802d804125891596e8ca5f291322",
            "version": "42.2.19"
          },
          {
            "name": "slf4j-api",
            "sha256": "cdba07964d1bb40a0761485c6b1e8c2f8fd9eb1d19c53928ac0d7f9510105c57",
            "version": "1.7.30"
          },
          {
            "name": "snakeyaml",
            "sha256": "7e7cce6740ed705bfdfaac7b442c1375d2986d2f2935936a5bd40c14e18fd736",
            "version": "1.27"
          },
          {
            "name": "spring-aop",
            "sha256": "18d08f02b52dc3734b22957200f53120595e41c5023400a6f82672abe5d0b38e",
            "version": "5.3.5"
          },
          {
            "name": "spring-beans",
            "sha256": "a5736e63f93f68f32d9785c3a4a74fd956b382d92a55cd123908141cf0d9b220",
            "version": "5.3.5"
          },
          {
            "name": "spring-boot",
            "sha256": "1f805f990961be541a4465978487e1cd35cdb078e8a8ec17ebf1553fecad2657",
            "version": "2.4.4"
          },
          {
            "name": "spring-boot-actuator",
            "sha256": "dbae617d0529c796cf50d9229bbcf5ffc1b2d4eb4f1f65d910b539860eff8360",
            "version": "2.4.4"
          },
          {
            "name": "spring-boot-actuator-autoconfigure",
            "sha256": "21274f14b84ada232ed79d4bab72937cb571434f5428c7ed2b7c97c29ad0f86a",
            "version": "2.4.4"
          },
          {
            "name": "spring-boot-autoconfigure",
            "sha256": "6d17db0d2662edf413020165f5cd955eecdd0f506973b57144055f69873b0f2d",
            "version": "2.4.4"
          },
          {
            "name": "spring-boot-jarmode-layertools",
            "sha256": "637b05a542e988e882c715750c7394a36b08af22ea084c6c117f63d0b2d01d55",
            "version": "2.4.4"
          },
          {
            "name": "spring-context",
            "sha256": "a00f670b873b9bf23829939c014c89ee0f0cca5777d05e7eb83cd26a4084898d",
            "version": "5.3.5"
          },
          {
            "name": "spring-core",
            "sha256": "4f25c412aed640d50200ef3b8013482d29878bd03eaa722af20b4fc7e2126d19",
            "version": "5.3.5"
          },
          {
            "name": "spring-expression",
            "sha256": "e4c7acd4aaaee2fa13ab9bdb664fdd29ee4b04d1046bbdff64cfbebcab651791",
            "version": "5.3.5"
          },
          {
            "name": "spring-jcl",
            "sha256": "e7d57085197d182f63d0c63dc40a545f10f30334889b5bda2d60a4287184ee1a",
            "version": "5.3.5"
          },
          {
            "name": "spring-jdbc",
            "sha256": "1bb609e5ca48e8a3e8c93f7f49867bc7a523d85713514f37117c74fb8fe68efa",
            "version": "5.3.5"
          },
          {
            "name": "spring-tx",
            "sha256": "f3653761d94adfa30904db68f837a235f0b5b2d35539a416266321c107d09b33",
            "version": "5.3.5"
          },
          {
            "name": "spring-web",
            "sha256": "3b1c02204ebf66916a2770db98438c2cd1b5cb870e3c1ed182a10c81e9e37e09",
            "version": "5.3.5"
          },
          {
            "name": "spring-webmvc",
            "sha256": "d663076cf51140e4a5a489cf6eced2af2457531a75d9cbf6ffb658b7be9d4099",
            "version": "5.3.5"
          },
          {
            "name": "tomcat-embed-core",
            "sha256": "35610e1de90902423345cb835c7c22e4c39d928a06e922d02bf8c5d0062d6249",
            "version": "9.0.44"
          },
          {
            "name": "tomcat-embed-websocket",
            "sha256": "33e62273dd1978745e1675f028f0291f064e390d30584b94549dfaacb0696151",
            "version": "9.0.44"
          }
        ],
        "layer": "application"
      },
      "buildpacks": {
        "id": "paketo-buildpacks/spring-boot",
        "version": "4.2.0"
      }
    },
    {
      "name": "helper",
      "metadata": {
        "layer": "helper",
        "names": [
          "spring-cloud-bindings"
        ],
        "version": "4.2.0"
      },
      "buildpacks": {
        "id": "paketo-buildpacks/spring-boot",
        "version": "4.2.0"
      }
    },
    {
      "name": "spring-cloud-bindings",
      "metadata": {
        "layer": "spring-cloud-bindings",
        "licenses": [
          {
            "type": "Apache-2.0",
            "uri": "https://github.com/spring-cloud/spring-cloud-bindings/blob/main/LICENSE"
          }
        ],
        "name": "Spring Cloud Bindings",
        "sha256": "a52c2592d58555b6d70a3b0128be70852c83a0c58b70a7b23c07ebd9631ec47a",
        "stacks": [
          "io.buildpacks.stacks.bionic",
          "org.cloudfoundry.stacks.cflinuxfs3"
        ],
        "uri": "https://repo.spring.io/release/org/springframework/cloud/spring-cloud-bindings/1.7.1/spring-cloud-bindings-1.7.1.jar",
        "version": "1.7.1"
      },
      "buildpacks": {
        "id": "paketo-buildpacks/spring-boot",
        "version": "4.2.0"
      }
    }
  ]
}

Stack is a combination of Build Image used as the base image for the build and Run Image used as the base image for the runtime. A typical stack can be checked with pack stack suggest command.

$ pack stack suggest
Stacks maintained by the community:

    Stack ID: heroku-18
    Description: The official Heroku stack based on Ubuntu 18.04
    Maintainer: Heroku
    Build Image: heroku/pack:18-build
    Run Image: heroku/pack:18

    Stack ID: io.buildpacks.stacks.bionic
    Description: A minimal Paketo stack based on Ubuntu 18.04
    Maintainer: Paketo Project
    Build Image: paketobuildpacks/build:base-cnb
    Run Image: paketobuildpacks/run:base-cnb

    Stack ID: io.buildpacks.stacks.bionic
    Description: A large Paketo stack based on Ubuntu 18.04
    Maintainer: Paketo Project
    Build Image: paketobuildpacks/build:full-cnb
    Run Image: paketobuildpacks/run:full-cnb

    Stack ID: io.paketo.stacks.tiny
    Description: A tiny Paketo stack based on Ubuntu 18.04, similar to distroless
    Maintainer: Paketo Project
    Build Image: paketobuildpacks/build:tiny-cnb
    Run Image: paketobuildpacks/run:tiny-cnb

As we have already confirmed with pack inspect command, the image created this time uses paketobuildpacks/run:base-cnb as the Run Image.

Rebase Stack

For example, if the Run Image is updated, you can use pack rebase command to update the Stack for the created image without changing the application layer.

Image Credit: https://buildpacks.io/docs/concepts/operations/rebase/

Run the following command.

$ pack rebase docker.io/library/vehicle-api:0.0.1-SNAPSHOT  
base-cnb: Pulling from paketobuildpacks/run
Digest: sha256:6c5acd07ae6820c606bb4aea6dd76c7db5bafa53b5b3b3f938267779966f8daf
Status: Image is up to date for paketobuildpacks/run:base-cnb
Rebasing docker.io/library/vehicle-api:0.0.1-SNAPSHOT on run image index.docker.io/paketobuildpacks/run:base-cnb
*** Images (660e5dedf241):
      docker.io/library/vehicle-api:0.0.1-SNAPSHOT
Rebased Image: 660e5dedf24107aad36c31585a289cd0849b1bf6709e414d287545ee589fda27
Successfully rebased image docker.io/library/vehicle-api:0.0.1-SNAPSHOT

As you can see Image is up to date for paketobuildpacks/run:base-cnb in the message, this Run Image is already up to date, so there is no change in this example. If you run pack inspect command on the rebased image, you can see that the Run Image ID is still て8e1ab607b6386d5d9455047383dccecc89cb4ecd8891f18e6aa35e7a686db162.

$ pack inspect docker.io/library/vehicle-api:0.0.1-SNAPSHOT 
Inspecting image: docker.io/library/vehicle-api:0.0.1-SNAPSHOT

REMOTE:
(not present)

LOCAL:

Stack: io.buildpacks.stacks.bionic

Base Image:
  Reference: 8e1ab607b6386d5d9455047383dccecc89cb4ecd8891f18e6aa35e7a686db162
  Top Layer: sha256:10aba31d775e5770b8dc5fc9a28047f71245d8bfdd7e5b7c15035f95ba46b8c5

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

Buildpacks:
  ID                                         VERSION        HOMEPAGE
  paketo-buildpacks/ca-certificates          2.1.0          https://github.com/paketo-buildpacks/ca-certificates
  paketo-buildpacks/bellsoft-liberica        7.1.0          https://github.com/paketo-buildpacks/bellsoft-liberica
  paketo-buildpacks/executable-jar           5.0.0          https://github.com/paketo-buildpacks/executable-jar
  paketo-buildpacks/dist-zip                 4.0.0          https://github.com/paketo-buildpacks/dist-zip
  paketo-buildpacks/spring-boot              4.2.0          https://github.com/paketo-buildpacks/spring-boot

Processes:
  TYPE                  SHELL        COMMAND        ARGS
  web (default)                      java           org.springframework.boot.loader.JarLauncher
  executable-jar                     java           org.springframework.boot.loader.JarLauncher
  task                               java           org.springframework.boot.loader.JarLauncher

Update Run Image

Now let's update the Run Image yourself. For example, suppose you find a vulnerability in OpenSSL and want to patch it yourself before the Run Image is officially updated. Create a run.Dockerfile file and write the following contents.

FROM index.docker.io/paketobuildpacks/run:base-cnb
USER root
RUN apt-get update && \
   apt-get upgrade -y openssl && \
   apt-get clean && \
   rm -rf /var/lib/apt/lists/*
USER cnb

Run the following command to create a Run Image.

$ docker build . -t run-image -f run.Dockerfile
[+] Building 14.7s (6/6) FINISHED                                                                                                                                                                                                                                                                  
 => [internal] load build definition from run.Dockerfile                                                                                                                                                                                                                                      0.0s
 => => transferring dockerfile: 233B                                                                                                                                                                                                                                                          0.0s
 => [internal] load .dockerignore                                                                                                                                                                                                                                                             0.0s
 => => transferring context: 2B                                                                                                                                                                                                                                                               0.0s
 => [internal] load metadata for docker.io/paketobuildpacks/run:base-cnb                                                                                                                                                                                                                      0.0s
 => CACHED [1/2] FROM docker.io/paketobuildpacks/run:base-cnb                                                                                                                                                                                                                                 0.0s
 => [2/2] RUN apt-get update &&     apt-get upgrade -y openssl &&     apt-get clean &&     rm -rf /var/lib/apt/lists/*                                                                                                                                                                       14.5s
 => exporting to image                                                                                                                                                                                                                                                                        0.1s 
 => => exporting layers                                                                                                                                                                                                                                                                       0.1s 
 => => writing image sha256:5348813eed51ae53b8a1bfb6ca81708125a18e80b425120c97079df461c01dd6                                                                                                                                                                                                  0.0s 
 => => naming to docker.io/library/run-image  

Specify the Run Image created by adding the --run-image option to pack rebase command.

$ pack rebase docker.io/library/vehicle-api:0.0.1-SNAPSHOT --run-image run-image
Rebasing docker.io/library/vehicle-api:0.0.1-SNAPSHOT on run image run-image
*** Images (dc92034129ab):
      docker.io/library/vehicle-api:0.0.1-SNAPSHOT
Rebased Image: dc92034129ab45bcacab0c2f11a7eb96dc5d2fb3aa48077494e6a8bc0188c04a
Successfully rebased image docker.io/library/vehicle-api:0.0.1-SNAPSHOT

If you run pack inspect command, you will see that the Run Image ID has changed.

$ pack inspect docker.io/library/vehicle-api:0.0.1-SNAPSHOT                     
Inspecting image: docker.io/library/vehicle-api:0.0.1-SNAPSHOT

REMOTE:
(not present)

LOCAL:

Stack: io.buildpacks.stacks.bionic

Base Image:
  Reference: 5348813eed51ae53b8a1bfb6ca81708125a18e80b425120c97079df461c01dd6
  Top Layer: sha256:7100daa8b77f6e5e0479bebad98656d454298701e0559c69d39155affa300e8c

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

Buildpacks:
  ID                                         VERSION        HOMEPAGE
  paketo-buildpacks/ca-certificates          2.1.0          https://github.com/paketo-buildpacks/ca-certificates
  paketo-buildpacks/bellsoft-liberica        7.1.0          https://github.com/paketo-buildpacks/bellsoft-liberica
  paketo-buildpacks/executable-jar           5.0.0          https://github.com/paketo-buildpacks/executable-jar
  paketo-buildpacks/dist-zip                 4.0.0          https://github.com/paketo-buildpacks/dist-zip
  paketo-buildpacks/spring-boot              4.2.0          https://github.com/paketo-buildpacks/spring-boot

Processes:
  TYPE                  SHELL        COMMAND        ARGS
  web (default)                      java           org.springframework.boot.loader.JarLauncher
  executable-jar                     java           org.springframework.boot.loader.JarLauncher
  task                               java           org.springframework.boot.loader.JarLauncher

You can see that this ID is for the image you created by running the following command:

$ docker images --no-trunc | grep 5348813eed51ae53b8a1bfb6ca81708125a18e80b425120c97079df461c01dd6
run-image                  latest                  sha256:5348813eed51ae53b8a1bfb6ca81708125a18e80b425120c97079df461c01dd6   About an hour ago   92.2MB

Build a Image with the updated run image

Let's create an image using the Run Image that you updated. For example, suppose you don't have the command you want to use for the default Run Image installed and you want to add it.

Update the run.Dockerfile file with the following content: Here, add the installation of ImageMagick.

FROM index.docker.io/paketobuildpacks/run:base-cnb
USER root
RUN apt-get update && \
   apt-get install -y --no-install-recommends imagemagick && \
   apt-get clean && \
   rm -rf /var/lib/apt/lists/*
USER cnb

Run the following command to create a Run Image.

$ docker build . -t run-image -f run.Dockerfile
[+] Building 20.0s (6/6) FINISHED                                                                                                                                                                                                                                                                  
 => [internal] load build definition from run.Dockerfile                                                                                                                                                                                                                                      0.0s
 => => transferring dockerfile: 261B                                                                                                                                                                                                                                                          0.0s
 => [internal] load .dockerignore                                                                                                                                                                                                                                                             0.0s
 => => transferring context: 2B                                                                                                                                                                                                                                                               0.0s
 => [internal] load metadata for docker.io/paketobuildpacks/run:base-cnb                                                                                                                                                                                                                      0.0s
 => CACHED [1/2] FROM docker.io/paketobuildpacks/run:base-cnb                                                                                                                                                                                                                                 0.0s
 => [2/2] RUN apt-get update &&     apt-get install -y --no-install-recommends imagemagick &&     apt-get clean &&     rm -rf /var/lib/apt/lists/*                                                                                                                                           19.6s
 => exporting to image                                                                                                                                                                                                                                                                        0.3s
 => => exporting layers                                                                                                                                                                                                                                                                       0.3s
 => => writing image sha256:6dccf4e29d3a8d0ec13e30a5053c9ddd85cca268565233b64b9362211e22f4f9                                                                                                                                                                                                  0.0s 
 => => naming to docker.io/library/run-image 

If you want to build an image using this Run Image with Spring Boot Maven Plugin, you can specify the image name with the spring-boot.build-image.runImage property. If Run Image exists only in local and NEVER is not specified with the spring-boot.build-image.pullPolicy property, an error will occur when trying to get from Remote.

./mvnw spring-boot:build-image \
 -Dmaven.test.skip=true \
 -Dspring-boot.build-image.runImage=run-image \
 -Dspring-boot.build-image.pullPolicy=NEVER

Run the image created with the following command and verify that you can use ImageMagick's convert command.

$ docker run --entrypoint bash --rm docker.io/library/vehicle-api:0.0.1-SNAPSHOT -c 'convert --version'
Version: ImageMagick 6.9.7-4 Q16 x86_64 20170114 http://www.imagemagick.org
Copyright: © 1999-2017 ImageMagick Studio LLC
License: http://www.imagemagick.org/script/license.php
Features: Cipher DPC Modules OpenMP 
Delegates (built-in): bzlib djvu fftw fontconfig freetype jbig jng jpeg lcms lqr ltdl lzma openexr pangocairo png tiff wmf x xml zlib


Configure buildpacks to use various functions provided by various buildpacks.

JVM Version

The JVM version used by the container defaults to 11 for the pack CLI and the Java version specified in pom.xml for the Spring Boot Maven Plugin.

If you want to change it, set the environment variable BP_JVM_VERSION at build time.

If you want to use Java 16, set the environment variables as follows.

pack build vehicle-api:16 \
 --builder paketobuildpacks/builder:base \
 --env BP_JVM_VERSION=16


The following log is output. You can see that JDK 16 (for build) and JRE 16 (for execution) have been downloaded.

base: Pulling from paketobuildpacks/builder
Digest: sha256:7f2b74ba47b235e5a30393ad83c3e3c12ff5229c190faffd67d842e064b7743e
Status: Downloaded newer image for paketobuildpacks/builder:base
base-cnb: Pulling from paketobuildpacks/run
Digest: sha256:aa9a8a0a4bb607422d3ff59923638f2a6cf3d1e6b404aee9ae027127ea88a5c1
Status: Image is up to date for paketobuildpacks/run:base-cnb
===> DETECTING
...
===> BUILDING

Paketo CA Certificates Buildpack 2.1.0
  https://github.com/paketo-buildpacks/ca-certificates
  Launch Helper: Contributing to layer
    Creating /layers/paketo-buildpacks_ca-certificates/helper/exec.d/ca-certificates-helper

Paketo BellSoft Liberica Buildpack 7.1.0
  https://github.com/paketo-buildpacks/bellsoft-liberica
  Build Configuration:
    $BP_JVM_VERSION              16              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 16.0.0: Contributing to layer
    Downloading from https://github.com/bell-sw/Liberica/releases/download/16+36/bellsoft-jdk16+36-linux-amd64.tar.gz
    Verifying checksum
    Expanding to /layers/paketo-buildpacks_bellsoft-liberica/jdk
    Adding 129 container CA certificates to JVM truststore
    Writing env.build/JAVA_HOME.override
    Writing env.build/JDK_HOME.override
  BellSoft Liberica JRE 16.0.0: Contributing to layer
    Downloading from https://github.com/bell-sw/Liberica/releases/download/16+36/bellsoft-jre16+36-linux-amd64.tar.gz
    Verifying checksum
    Expanding to /layers/paketo-buildpacks_bellsoft-liberica/jre
    Adding 129 container CA certificates to JVM truststore
    Writing env.launch/BPI_APPLICATION_PATH.default
    Writing env.launch/BPI_JVM_CACERTS.default
    Writing env.launch/BPI_JVM_CLASS_COUNT.default
    Writing env.launch/BPI_JVM_SECURITY_PROVIDERS.default
    Writing env.launch/JAVA_HOME.default
    Writing env.launch/MALLOC_ARENA_MAX.default
...
Saving vehicle-api:16...
*** Images (6cc5e40ea762):
      vehicle-api:16
Adding cache layer 'paketo-buildpacks/bellsoft-liberica:jdk'
Adding cache layer 'paketo-buildpacks/maven:application'
Adding cache layer 'paketo-buildpacks/maven:cache'
Successfully built image vehicle-api:16

For Spring Boot Maven Plugin, set pom.xml as follows and execute ./mvnw spring-boot:build-image.

<plugin>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-maven-plugin</artifactId>
  <configuration>
     <image>
        <env>
           <BP_JVM_VERSION>16</BP_JVM_VERSION>
        </env>
     </image>
  </configuration>
</plugin>

If you run the created image vehicle-api:16 and check the log, you can see that Java 16 is used.

$ docker run --rm \
 -p 8080:8080 \
 -m 1g \
 -e SPRING_DATASOURCE_URL=jdbc:postgresql://host.docker.internal:5432/vehicle \
 vehicle-api:16
Setting Active Processor Count to 4
Calculated JVM Memory Configuration: -XX:MaxDirectMemorySize=10M -Xmx449786K -XX:MaxMetaspaceSize=86789K -XX:ReservedCodeCacheSize=240M -Xss1M (Total Memory: 1G, Thread Count: 250, Loaded Class Count: 12909, Headroom: 0%)
Adding 129 container CA certificates to JVM truststore
Spring Cloud Bindings Enabled
Picked up JAVA_TOOL_OPTIONS: -Djava.security.properties=/layers/paketo-buildpacks_bellsoft-liberica/java-security-properties/java-security.properties -agentpath:/layers/paketo-buildpacks_bellsoft-liberica/jvmkill/jvmkill-1.16.0-RELEASE.so=printHeapHistogram=1 -XX:ActiveProcessorCount=4 -XX:MaxDirectMemorySize=10M -Xmx449786K -XX:MaxMetaspaceSize=86789K -XX:ReservedCodeCacheSize=240M -Xss1M -Dorg.springframework.cloud.bindings.boot.enable=true

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v2.4.4)

2021-04-10 15:11:33.843  INFO 1 --- [           main] com.example.VehicleApiApplication        : Starting VehicleApiApplication v0.0.1-SNAPSHOT using Java 16 on e80d92234e74 with PID 1 (/workspace/BOOT-INF/classes started by cnb in /workspace)

...

Memory Calculator

For Java applications created by Paketo Buildpack, the memory setting is automatically done as shown in the following figure.

The area of Java Memory that is set automatically is Non Heap. After determining the value of Non Heap, the maximum value of Heap (-Xmx) is calculated by the following formula.

Heap = Container Memory - Non Heap - Headroom

The value of Non Heap is calculated by the following formula.

Non Heap = Metaspace + Reserved CodeCache + Direct Memory + Tread Stack

Of these, the following items are fixed values by default.

Metaspace is automatically calculated from the number of classes loaded. The number of classes loaded is estimated by the number of class files * 0.35 contained in the jar or war file.

In other words, the Servlet app will be secured at 500MB in addition to Heap and Metaspace, and the Spring Web Flux app will be secured at 300MB.

Run the following command.

docker run --rm \
 -p 8080:8080 \
 -m 1g \
 -e SPRING_DATASOURCE_URL=jdbc:postgresql://host.docker.internal:5432/vehicle \
 docker.io/library/vehicle-api:0.0.1-SNAPSHOT

Immediately after, the calculated memory setting is output as the following log.

Calculated JVM Memory Configuration: -XX:MaxDirectMemorySize=10M -Xmx449112K -XX:MaxMetaspaceSize=87463K -XX:ReservedCodeCacheSize=240M -Xss1M (Total Memory: 1G, Thread Count: 250, Loaded Class Count: 13028, Headroom: 0%)

The number of classes loaded is estimated to be 13,028, from which the Metaspace value is automatically calculated to be 87,436KB. Since the container memory is specified as 1GB, the set Heap size is calculated as 1GB - (87,463KB + 500MB) - 0B = 449,112KB. The configuration is as shown in the following figure.

Reduce heap

As mentioned above, the Non Heap size is automatically determined by the number of .class files, so it will be a constant value for the same jar or war file. Therefore, reducing the memory size of the container will inevitably reduce the Heap size.

Run the following command. Reduced the memory size of the container to 768MB.

docker run --rm \
 -p 8080:8080 \
 -m 768m \
 -e SPRING_DATASOURCE_URL=jdbc:postgresql://host.docker.internal:5432/vehicle \
 docker.io/library/vehicle-api:0.0.1-SNAPSHOT

The following log will be output.

Calculated JVM Memory Configuration: -XX:MaxDirectMemorySize=10M -Xmx186968K -XX:MaxMetaspaceSize=87463K -XX:ReservedCodeCacheSize=240M -Xss1M (Total Memory: 768M, Thread Count: 250, Loaded Class Count: 13028, Headroom: 0%)

You can see that the Heap size (-Xmx) is smaller than when the container memory size is 1GB, and otherwise it is the same value. The configuration is as shown in the following figure.

Let's reduce the memory size of the container further to 512MB. Run the following command.

docker run --rm \
 -p 8080:8080 \
 -m 512m \
 -e SPRING_DATASOURCE_URL=jdbc:postgresql://host.docker.internal:5432/vehicle \
 docker.io/library/vehicle-api:0.0.1-SNAPSHOT

An error will occur and the following log will be output.

unable to calculate memory configuration
fixed memory regions require 599463K which is greater than 512M available for allocation: -XX:MaxDirectMemorySize=10M, -XX:MaxMetaspaceSize=87463K, -XX:ReservedCodeCacheSize=240M, -Xss1M * 250 threads
ERROR: failed to launch: exec.d: failed to execute exec.d file at path '/layers/paketo-buildpacks_bellsoft-liberica/helper/exec.d/memory-calculator': exit status 1

The error occurred because the memory size of the container was smaller than the size required for the Non Heap and there was no room for the Heap.

Reduce thread count

Let's change the Non Heap size so that the container size can be reduced to 512MB. First, try reducing the number of threads. The number of threads can be specified by the runtime environment variable BPL_JVM_THREAD_COUNT. Run the following command.

docker run --rm \
 -p 8080:8080 \
 -m 512m \
 -e BPL_JVM_THREAD_COUNT=30 \
 -e SPRING_DATASOURCE_URL=jdbc:postgresql://host.docker.internal:5432/vehicle \
 docker.io/library/vehicle-api:0.0.1-SNAPSHOT

The following log will be output.

Calculated JVM Memory Configuration: -XX:MaxDirectMemorySize=10M -Xmx150104K -XX:MaxMetaspaceSize=87463K -XX:ReservedCodeCacheSize=240M -Xss1M (Total Memory: 512M, Thread Count: 30, Loaded Class Count: 13028, Headroom: 0%)

The JVM options for the Non Heap haven't changed, but the number of threads has been reduced from 250 to 30 and there is room to allocate memory for the thread stack size of 220MB (= 1MB * 220) for the Heap, so the application started without error. The configuration is as shown in the following figure.

Configure loaded class count

Metaspace is determined by the number of classes loaded, and the number of classes loaded is estimated from the number of .class files. If the number of classes actually loaded is greater than or less than the estimated number, you can specify it with the runtime environment variable BPL_JVM_LOADED_CLASS_COUNT.

In the previous example, the number of classes was estimated to be 13,028, but here we specify 10,000. Run the following command.

docker run --rm \
 -p 8080:8080 \
 -m 512m \
 -e BPL_JVM_THREAD_COUNT=30 \
 -e BPL_JVM_LOADED_CLASS_COUNT=10000 \
 -e SPRING_DATASOURCE_URL=jdbc:postgresql://host.docker.internal:5432/vehicle \
 docker.io/library/vehicle-api:0.0.1-SNAPSHOT

The following log will be output.

Calculated JVM Memory Configuration: -XX:MaxDirectMemorySize=10M -Xmx167255K -XX:MaxMetaspaceSize=70312K -XX:ReservedCodeCacheSize=240M -Xss1M (Total Memory: 512M, Thread Count: 30, Loaded Class Count: 10000, Headroom: 0%)

The configuration is as shown in the following figure.

Configure headroom

Depending on the runtime environment, memory other than Java may be required. If the JVM is not out of memory but the container is out of memory, increase the headroom. Run the following command. Allocate 5% of the container's memory size to something other than Java.

docker run --rm \
 -p 8080:8080 \
 -m 512m \
 -e BPL_JVM_THREAD_COUNT=30 \
 -e BPL_JVM_HEADROOM=5 \
 -e SPRING_DATASOURCE_URL=jdbc:postgresql://host.docker.internal:5432/vehicle \
 docker.io/library/vehicle-api:0.0.1-SNAPSHOT

The following log will be output.

Calculated JVM Memory Configuration: -XX:MaxDirectMemorySize=10M -Xmx123890K -XX:MaxMetaspaceSize=87463K -XX:ReservedCodeCacheSize=240M -Xss1M (Total Memory: 512M, Thread Count: 30, Loaded Class Count: 13028, Headroom: 5%)

The configuration is as shown in the following figure.

Reduce reserved code cache and thread stack

Other areas of Non Heap are set to fixed values by default, but can be overridden by the environment variable JAVA_TOOL_OPTIONS. Try reducing these values to reduce the container memory size to 256MB.

Run the following command.

docker run --rm \
 -p 8080:8080 \
 -m 256m \
 -e BPL_JVM_THREAD_COUNT=30 \
 -e BPL_JVM_HEADROOM=5 \
 -e JAVA_TOOL_OPTIONS="-XX:ReservedCodeCacheSize=64M -Xss512k" \
 -e SPRING_DATASOURCE_URL=jdbc:postgresql://host.docker.internal:5432/vehicle \
 docker.io/library/vehicle-api:0.0.1-SNAPSHOT

The following log will be output.

Calculated JVM Memory Configuration: -XX:MaxDirectMemorySize=10M -Xmx70437K -XX:MaxMetaspaceSize=87463K (Total Memory: 256M, Thread Count: 30, Loaded Class Count: 13028, Headroom: 5%)

The configuration is as shown in the following figure.

Explained how to adjust the Memory Calculator. By default, the Memory Calculator provides settings that can withstand high loads. If you want to reduce the value, be aware that there is a trade-off and change it.

Debbuging

The Paketo Debug Buildpack allows you to debug your container's application using the IDE.

To enable Debug, you need to set environment variables both at build time and at run time.

First, set the environment variable BP_DEBUG_ENABLED to true at build time to add a file for debugging (a command to add debug options to the environment variable JAVA_TOOL_OPTIONS) in the container image. Run the following command.

pack build vehicle-api \
 --builder paketobuildpacks/builder:base \
 --env BP_DEBUG_ENABLED=true


The following log is output. You can see that the log related to Paketo Debug Buildpack is output.

base: Pulling from paketobuildpacks/builder
Digest: sha256:7f2b74ba47b235e5a30393ad83c3e3c12ff5229c190faffd67d842e064b7743e
Status: Image is up to date for paketobuildpacks/builder:base
base-cnb: Pulling from paketobuildpacks/run
Digest: sha256:aa9a8a0a4bb607422d3ff59923638f2a6cf3d1e6b404aee9ae027127ea88a5c1
Status: Image is up to date for paketobuildpacks/run:base-cnb
===> DETECTING
8 of 18 buildpacks participating
paketo-buildpacks/ca-certificates   2.1.0
paketo-buildpacks/bellsoft-liberica 7.1.0
paketo-buildpacks/maven             5.0.0
paketo-buildpacks/executable-jar    5.0.0
paketo-buildpacks/apache-tomcat     5.1.0
paketo-buildpacks/dist-zip          4.0.0
paketo-buildpacks/spring-boot       4.2.0
paketo-buildpacks/debug             3.0.0
===> ANALYZING
...
===> BUILDING

Paketo CA Certificates Buildpack 2.1.0
  https://github.com/paketo-buildpacks/ca-certificates
  Launch Helper: Reusing cached layer

Paketo BellSoft Liberica Buildpack 7.1.0
  https://github.com/paketo-buildpacks/bellsoft-liberica
  Build Configuration:
    $BP_JVM_VERSION              11              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 11.0.10: Reusing cached layer
  BellSoft Liberica JRE 11.0.10: Reusing cached layer
  Launch Helper: Reusing cached layer
  JVMKill Agent 1.16.0: Reusing cached layer
  Java Security Properties: Reusing cached layer

Paketo Maven Buildpack 5.0.0
  https://github.com/paketo-buildpacks/maven
  Build Configuration:
    $BP_MAVEN_BUILD_ARGUMENTS  -Dmaven.test.skip=true package  the arguments to pass to Maven
    $BP_MAVEN_BUILT_ARTIFACT   target/*.[jw]ar                 the built application artifact explicitly.  Supersedes $BP_MAVEN_BUILT_MODULE
    $BP_MAVEN_BUILT_MODULE                                     the module to find application artifact in
    Creating cache directory /home/cnb/.m2
  Compiled Application: Contributing to layer
    Executing mvnw --batch-mode -Dmaven.test.skip=true package
[INFO] Scanning for projects...
[INFO] 
[INFO] ----------------------< com.example:vehicle-api >-----------------------
[INFO] Building demo 0.0.1-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
...
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  1.747 s
[INFO] Finished at: 2021-04-10T13:34:12Z
[INFO] ------------------------------------------------------------------------
  Removing source code

Paketo Executable JAR Buildpack 5.0.0
  https://github.com/paketo-buildpacks/executable-jar
  Class Path: Contributing to layer
    Writing env/CLASSPATH.delim
    Writing env/CLASSPATH.prepend
  Process types:
    executable-jar: java org.springframework.boot.loader.JarLauncher (direct)
    task:           java org.springframework.boot.loader.JarLauncher (direct)
    web:            java org.springframework.boot.loader.JarLauncher (direct)

Paketo Spring Boot Buildpack 4.2.0
  https://github.com/paketo-buildpacks/spring-boot
  Creating slices from layers index
    dependencies
    spring-boot-loader
    snapshot-dependencies
    application
  Launch Helper: Reusing cached layer
  Spring Cloud Bindings 1.7.1: Reusing cached layer
  Web Application Type: Contributing to layer
    Servlet web application detected
    Writing env.launch/BPL_JVM_THREAD_COUNT.default
  4 application slices
  Image labels:
    org.opencontainers.image.title
    org.opencontainers.image.version
    org.springframework.boot.spring-configuration-metadata.json
    org.springframework.boot.version

Paketo Debug Buildpack 3.0.0
  https://github.com/paketo-buildpacks/debug
  Build Configuration:
    $BP_DEBUG_ENABLED   true  whether to contribute debug support
  Launch Configuration:
    $BPL_DEBUG_ENABLED        whether to enable debug support
    $BPL_DEBUG_PORT     8080  what port the debug agent will listen on
    $BPL_DEBUG_SUSPEND  n     whether the JVM will suspend execution until a debugger has attached
  Launch Helper: Contributing to layer
    Creating /layers/paketo-buildpacks_debug/helper/exec.d/debug
===> EXPORTING
...
Saving vehicle-api...
*** Images (048a01061034):
      vehicle-api
Reusing cache layer 'paketo-buildpacks/bellsoft-liberica:jdk'
Adding cache layer 'paketo-buildpacks/maven:application'
Reusing cache layer 'paketo-buildpacks/maven:cache'
Successfully built image vehicle-api

For Spring Boot Maven Plugin, set pom.xml as follows and execute ./mvnw spring-boot:build-image

<plugin>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-maven-plugin</artifactId>
  <configuration>
     <image>
        <env>
           <BP_DEBUG_ENABLED>true</BP_DEBUG_ENABLED>
        </env>
     </image>
  </configuration>
</plugin>

Then start the container with the environment variable BPL_DEBUG_ENABLED set to true. Expose 8000 as a port for debug connections. Run the following command.

docker run --rm \
 -p 8080:8080 \
 -p 8000:8000 \
 -m 1g \
 -e BPL_DEBUG_ENABLED=true \
 -e BPL_DEBUG_PORT="*:8000" \
 -e SPRING_DATASOURCE_URL=jdbc:postgresql://host.docker.internal:5432/vehicle \
 vehicle-api


The following log is output. You can see that the environment variable JAVA_TOOL_OPTIONS has an option for debugging.

Setting Active Processor Count to 4
Calculated JVM Memory Configuration: -XX:MaxDirectMemorySize=10M -Xmx449112K -XX:MaxMetaspaceSize=87463K -XX:ReservedCodeCacheSize=240M -Xss1M (Total Memory: 1G, Thread Count: 250, Loaded Class Count: 13028, Headroom: 0%)
Adding 129 container CA certificates to JVM truststore
Spring Cloud Bindings Enabled
Debugging enabled on port *:8000
Picked up JAVA_TOOL_OPTIONS: -Djava.security.properties=/layers/paketo-buildpacks_bellsoft-liberica/java-security-properties/java-security.properties -agentpath:/layers/paketo-buildpacks_bellsoft-liberica/jvmkill/jvmkill-1.16.0-RELEASE.so=printHeapHistogram=1 -XX:ActiveProcessorCount=4 -XX:MaxDirectMemorySize=10M -Xmx449112K -XX:MaxMetaspaceSize=87463K -XX:ReservedCodeCacheSize=240M -Xss1M -Dorg.springframework.cloud.bindings.boot.enable=true -agentlib:jdwp=transport=dt_socket,server=y,address=*:8000,suspend=n
Listening for transport dt_socket at address: 8000

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v2.4.4)
...

Debug from the IDE. Open the source code in the IDE.

For IntelliJ IDEA, make the following settings and execute debugging.

Set a breakpoint in your source code and access your application to execute that code.


For Eclipse, please refer to this document.

JMX

Use Paketo JMX Buildpack to enable JMX settings for your application. The mechanism is almost the same as Debug Buildpack.

First, set the environment variable BP_JMX_ENABLED to true at build time to add a JMX configuration file (a command to add JMX options to the environment variable JAVA_TOOL_OPTIONS) in the container image. Run the following command.

pack build vehicle-api \
 --builder paketobuildpacks/builder:base \
 --env BP_JMX_ENABLED=true

The following log is output. You can see that the log related to Paketo JMX Buildpack is output.

base: Pulling from paketobuildpacks/builder
Digest: sha256:25fc8c5ab1fb229415e58cab0bd1bb1fa8a6b96ba40867cb8e94541ed618de1e
Status: Image is up to date for paketobuildpacks/builder:base
base-cnb: Pulling from paketobuildpacks/run
Digest: sha256:aa9a8a0a4bb607422d3ff59923638f2a6cf3d1e6b404aee9ae027127ea88a5c1
Status: Downloaded newer image for paketobuildpacks/run:base-cnb
===> DETECTING
8 of 18 buildpacks participating
paketo-buildpacks/ca-certificates   2.1.0
paketo-buildpacks/bellsoft-liberica 7.1.0
paketo-buildpacks/maven             5.0.0
paketo-buildpacks/executable-jar    5.0.0
paketo-buildpacks/apache-tomcat     5.1.0
paketo-buildpacks/dist-zip          4.0.0
paketo-buildpacks/spring-boot       4.2.0
paketo-buildpacks/jmx               3.0.0
===> ANALYZING
...
===> RESTORING
Restoring data for "paketo-buildpacks/bellsoft-liberica:jdk" from cache
Restoring data for "paketo-buildpacks/maven:application" from cache
Restoring data for "paketo-buildpacks/maven:cache" from cache
===> BUILDING

Paketo CA Certificates Buildpack 2.1.0
  https://github.com/paketo-buildpacks/ca-certificates
  Launch Helper: Reusing cached layer

Paketo BellSoft Liberica Buildpack 7.1.0
  https://github.com/paketo-buildpacks/bellsoft-liberica
  Build Configuration:
    $BP_JVM_VERSION              11              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 11.0.10: Reusing cached layer
  BellSoft Liberica JRE 11.0.10: Reusing cached layer
  Launch Helper: Reusing cached layer
  JVMKill Agent 1.16.0: Reusing cached layer
  Java Security Properties: Reusing cached layer

Paketo Maven Buildpack 5.0.0
  https://github.com/paketo-buildpacks/maven
  Build Configuration:
    $BP_MAVEN_BUILD_ARGUMENTS  -Dmaven.test.skip=true package  the arguments to pass to Maven
    $BP_MAVEN_BUILT_ARTIFACT   target/*.[jw]ar                 the built application artifact explicitly.  Supersedes $BP_MAVEN_BUILT_MODULE
    $BP_MAVEN_BUILT_MODULE                                     the module to find application artifact in
    Creating cache directory /home/cnb/.m2
  Compiled Application: Contributing to layer
    Executing mvnw --batch-mode -Dmaven.test.skip=true package
[INFO] Scanning for projects...
[INFO] 
[INFO] ----------------------< com.example:vehicle-api >-----------------------
[INFO] Building demo 0.0.1-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
...
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  2.321 s
[INFO] Finished at: 2021-04-09T13:29:31Z
[INFO] ------------------------------------------------------------------------
  Removing source code

Paketo Executable JAR Buildpack 5.0.0
  https://github.com/paketo-buildpacks/executable-jar
  Class Path: Contributing to layer
    Writing env/CLASSPATH.delim
    Writing env/CLASSPATH.prepend
  Process types:
    executable-jar: java org.springframework.boot.loader.JarLauncher (direct)
    task:           java org.springframework.boot.loader.JarLauncher (direct)
    web:            java org.springframework.boot.loader.JarLauncher (direct)

Paketo Spring Boot Buildpack 4.2.0
  https://github.com/paketo-buildpacks/spring-boot
  Creating slices from layers index
    dependencies
    spring-boot-loader
    snapshot-dependencies
    application
  Launch Helper: Reusing cached layer
  Spring Cloud Bindings 1.7.1: Reusing cached layer
  Web Application Type: Reusing cached layer
  4 application slices
  Image labels:
    org.opencontainers.image.title
    org.opencontainers.image.version
    org.springframework.boot.spring-configuration-metadata.json
    org.springframework.boot.version

Paketo JMX Buildpack 3.0.0
  https://github.com/paketo-buildpacks/jmx
  Build Configuration:
    $BP_JMX_ENABLED   true  whether to contribute JMX support
  Launch Configuration:
    $BPL_JMX_ENABLED        whether to enable JMX support
    $BPL_JMX_PORT     5000  what port the JMX Connector will listen on
  Launch Helper: Contributing to layer
    Creating /layers/paketo-buildpacks_jmx/helper/exec.d/jmx
===> EXPORTING
...
Saving vehicle-api...
*** Images (98b6f417d46c):
      vehicle-api
Reusing cache layer 'paketo-buildpacks/bellsoft-liberica:jdk'
Adding cache layer 'paketo-buildpacks/maven:application'
Reusing cache layer 'paketo-buildpacks/maven:cache'
Successfully built image vehicle-api

For Spring Boot Maven Plugin, set pom.xml as follows and execute ./mvnw spring-boot:build-image.

<plugin>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-maven-plugin</artifactId>
  <configuration>
     <image>
        <env>
           <BP_JMX_ENABLED>true</BP_JMX_ENABLED>
        </env>
     </image>
  </configuration>
</plugin>

Then start the container with the environment variable BPL_JMX_ENABLED set to true. Publish 5000 as a port for JMX. Execute the following command.

docker run --rm \
 -p 8080:8080 \
 -p 5000:5000 \
 -m 1g \
 -e BPL_JMX_ENABLED=true \
 -e SPRING_DATASOURCE_URL=jdbc:postgresql://host.docker.internal:5432/vehicle \
 vehicle-api

The following log is output. You can see that the environment variable JAVA_TOOL_OPTIONS has an option for JMX.

Setting Active Processor Count to 4
Calculated JVM Memory Configuration: -XX:MaxDirectMemorySize=10M -Xmx449112K -XX:MaxMetaspaceSize=87463K -XX:ReservedCodeCacheSize=240M -Xss1M (Total Memory: 1G, Thread Count: 250, Loaded Class Count: 13028, Headroom: 0%)
Adding 129 container CA certificates to JVM truststore
Spring Cloud Bindings Enabled
JMX enabled on port 5000
Picked up JAVA_TOOL_OPTIONS: -Djava.security.properties=/layers/paketo-buildpacks_bellsoft-liberica/java-security-properties/java-security.properties -agentpath:/layers/paketo-buildpacks_bellsoft-liberica/jvmkill/jvmkill-1.16.0-RELEASE.so=printHeapHistogram=1 -XX:ActiveProcessorCount=4 -XX:MaxDirectMemorySize=10M -Xmx449112K -XX:MaxMetaspaceSize=87463K -XX:ReservedCodeCacheSize=240M -Xss1M -Dorg.springframework.cloud.bindings.boot.enable=true -Djava.rmi.server.hostname=127.0.0.1 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.port=5000 -Dcom.sun.management.jmxremote.rmi.port=5000

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v2.4.4)
...

Use JConsole to access localhost:5000.

Environment variables are often used when setting information for connecting to an external service such as from an application to a database, but Binding treats an instance of an external service as a bindable resource and binds this resource to the application. It is a mechanism to inject connection information with. There is no need to explicitly set environment variables on the application side.

The "Attach" used in the description of IV. Backing services in The Twelve-Factor App stands for Binding.

Image Credit: https://12factor.net/backing-services

As an implementation of Binding, there are

The former also covers the latter, and the latter is Deprecated. Paketo Buildpack supports these Binding mechanisms. This Binding mechanism provides connection information to various services. For example, here are the settings for connecting to APM like Azure Application Insights and Google Stackdriver.

The connection information to the service is obtained via VolumeMount instead of the environment variable.

PostgreSQL

So far, the application has set the environment variable SPRING_DATASOURCE_URL to access PostgreSQL, but this time we will use Binding to pass the connection information to PostgreSQL.

Create connection information according to the Service Binding Specification for Kubernetes.

Run the following command in the vehicle-api directory.

mkdir -p bindings/vehicle-db
echo postgresql > bindings/vehicle-db/type
echo host.docker.internal > bindings/vehicle-db/host
echo 5432 > bindings/vehicle-db/port
echo vehicle > bindings/vehicle-db/username
echo vehicle > bindings/vehicle-db/password
echo vehicle > bindings/vehicle-db/database

The file structure is as follows. Information for binding to a resource called vehicle-db is set in each file under that directory.

$ tree bindings 
bindings
`-- vehicle-db
    |-- database
    |-- host
    |-- password
    |-- port
    |-- type
    `-- username

1 directory, 6 files

Spring Cloud Bindings is a library that converts this file structure into Spring Boot properties. If you created a container image with Paketo Java Buildpack, the Spring Cloud Bindings jar will be added automatically.

https://github.com/spring-cloud/spring-cloud-bindings#postgresql-rdbms

Mount the bindings directory with the connection information in /bindings of the container and set this directory to the environment variable SERVICE_BINDING_ROOT according to Service Binding Specification for Kubernetes. Execute the following command.

docker run --rm \
 -p 8080:8080 \
 -m 1g \
 -e SERVICE_BINDING_ROOT=/bindings \
 -e MANAGEMENT_ENDPOINTS_WEB_EXPOSURE_INCLUDE="*" \
 -v ${PWD}/bindings:/bindings \
 docker.io/library/vehicle-api:0.0.1-SNAPSHOT

The following log is output.

Setting Active Processor Count to 4
Calculated JVM Memory Configuration: -XX:MaxDirectMemorySize=10M -Xmx449112K -XX:MaxMetaspaceSize=87463K -XX:ReservedCodeCacheSize=240M -Xss1M (Total Memory: 1G, Thread Count: 250, Loaded Class Count: 13028, Headroom: 0%)
Adding 129 container CA certificates to JVM truststore
Spring Cloud Bindings Enabled
Picked up JAVA_TOOL_OPTIONS: -Djava.security.properties=/layers/paketo-buildpacks_bellsoft-liberica/java-security-properties/java-security.properties -agentpath:/layers/paketo-buildpacks_bellsoft-liberica/jvmkill/jvmkill-1.16.0-RELEASE.so=printHeapHistogram=1 -XX:ActiveProcessorCount=4 -XX:MaxDirectMemorySize=10M -Xmx449112K -XX:MaxMetaspaceSize=87463K -XX:ReservedCodeCacheSize=240M -Xss1M -Dorg.springframework.cloud.bindings.boot.enable=true

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v2.4.4)

2021-04-10 09:46:07.569  INFO 1 --- [           main] com.example.VehicleApiApplication        : Starting VehicleApiApplication v0.0.1-SNAPSHOT using Java 11.0.10 on b7b649589f37 with PID 1 (/workspace/BOOT-INF/classes started by cnb in /workspace)
2021-04-10 09:46:07.571  INFO 1 --- [           main] com.example.VehicleApiApplication        : No active profile set, falling back to default profiles: default
2021-04-10 09:46:07.644  INFO 1 --- [           main] .BindingSpecificEnvironmentPostProcessor : Creating binding-specific PropertySource from Kubernetes Service Bindings
2021-04-10 09:46:08.612  INFO 1 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2021-04-10 09:46:08.621  INFO 1 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2021-04-10 09:46:08.622  INFO 1 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.44]
2021-04-10 09:46:08.663  INFO 1 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2021-04-10 09:46:08.664  INFO 1 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1006 ms
2021-04-10 09:46:08.853  INFO 1 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
2021-04-10 09:46:08.953  INFO 1 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
2021-04-10 09:46:09.111  INFO 1 --- [           main] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'
2021-04-10 09:46:09.373  INFO 1 --- [           main] o.s.b.a.e.web.EndpointLinksResolver      : Exposing 13 endpoint(s) beneath base path '/actuator'
2021-04-10 09:46:09.409  INFO 1 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2021-04-10 09:46:09.421  INFO 1 --- [           main] com.example.VehicleApiApplication        : Started VehicleApiApplication in 2.25 seconds (JVM running for 2.582)

If the application is running, you have successfully connected to PostgreSQL using Binding.

You can check the properties automatically set by Spring Cloud Bindings with the following command.

$ curl -s http://localhost:8080/actuator/env | jq ".propertySources[] | select(.name == \"kubernetesServiceBindingSpecific\")" 
{
  "name": "kubernetesServiceBindingSpecific",
  "properties": {
    "spring.datasource.driver-class-name": {
      "value": "org.postgresql.Driver"
    },
    "spring.datasource.username": {
      "value": "vehicle"
    },
    "spring.datasource.url": {
      "value": "jdbc:postgresql://host.docker.internal:5432/vehicle"
    },
    "spring.r2dbc.password": {
      "value": "******"
    },
    "spring.r2dbc.url": {
      "value": "r2dbc:postgresql://host.docker.internal:5432/vehicle"
    },
    "spring.r2dbc.username": {
      "value": "vehicle"
    },
    "spring.datasource.password": {
      "value": "******"
    }
  }
}

It also sets the following properties for the following Bindings:

$ curl -s http://localhost:8080/actuator/env | jq ".propertySources[] | select(.name == \"kubernetesServiceBindingFlattened\")"
{
  "name": "kubernetesServiceBindingFlattened",
  "properties": {
    "k8s.bindings.vehicle-db.password": {
      "value": "******"
    },
    "k8s.bindings.vehicle-db.host": {
      "value": "host.docker.internal"
    },
    "k8s.bindings.vehicle-db.port": {
      "value": "5432"
    },
    "k8s.bindings.vehicle-db.database": {
      "value": "vehicle"
    },
    "k8s.bindings.vehicle-db.username": {
      "value": "vehicle"
    }
  }
}

CA certificates

Next, pass the trusted CA certificate to the container using the Binding mechanism. Trust the CA of the certificate you set on the PostgreSQL server so that the vehicle-api connects to PostgreSQL with TLS.

First, set self-signed TLS in PostgreSQL. Create a certs directory under the vehicle-api directory, create a file called generate-certs.sh in that directory, and write the following contents.

#!/bin/bash
set -ex

cd /certs
openssl req -new -nodes -out root.csr \
 -keyout root.key -subj "/CN=docker.internal"
chmod og-rwx root.key

openssl x509 -req -in root.csr -days 3650 \
 -extfile /etc/ssl/openssl.cnf -extensions v3_ca \
 -signkey root.key -out root.crt

openssl req -new -nodes -out server.csr \
 -keyout server.key -subj "/CN=host.docker.internal"
chmod og-rwx server.key

openssl x509 -req -in server.csr -days 3650 \
 -CA root.crt -CAkey root.key -CAcreateserial \
 -out server.crt

Run the following command in the vehicle-api directory.

docker run --rm \
 -v ${PWD}/certs:/certs \
 paketobuildpacks/run:base-cnb \
 sh /certs/generate-certs.sh

The following log is output.

+ cd /certs
+ openssl req -new -nodes -out root.csr -keyout root.key -subj /CN=docker.internal
Can't load /home/cnb/.rnd into RNG
139648000373184:error:2406F079:random number generator:RAND_load_file:Cannot open file:../crypto/rand/randfile.c:88:Filename=/home/cnb/.rnd
Generating a RSA private key
.+++++
..................+++++
writing new private key to 'root.key'
-----
+ chmod og-rwx root.key
+ openssl x509 -req -in root.csr -days 3650 -extfile /etc/ssl/openssl.cnf -extensions v3_ca -signkey root.key -out root.crt
Signature ok
subject=CN = docker.internal
Getting Private key
+ openssl req -new -nodes -out server.csr -keyout server.key -subj /CN=host.docker.internal
Can't load /home/cnb/.rnd into RNG
140412413399488:error:2406F079:random number generator:RAND_load_file:Cannot open file:../crypto/rand/randfile.c:88:Filename=/home/cnb/.rnd
Generating a RSA private key
.........+++++
....+++++
writing new private key to 'server.key'
-----
+ chmod og-rwx server.key
+ openssl x509 -req -in server.csr -days 3650 -CA root.crt -CAkey root.key -CAcreateserial -out server.crt
Signature ok
subject=CN = host.docker.internal
Getting CA Private Key

Make sure that the following files are created under the certs directory:

$ ls -l certs 
total 64
-rw-r--r--  1 toshiaki  staff   499 Apr 10 02:14 generate-certs.sh
-rw-r--r--  1 toshiaki  staff  1131 Apr 10 02:14 root.crt
-rw-r--r--  1 toshiaki  staff   899 Apr 10 02:14 root.csr
-rw-------  1 toshiaki  staff  1704 Apr 10 02:14 root.key
-rw-r--r--  1 toshiaki  staff    41 Apr 10 02:14 root.srl
-rw-r--r--  1 toshiaki  staff  1013 Apr 10 02:14 server.crt
-rw-r--r--  1 toshiaki  staff   907 Apr 10 02:14 server.csr
-rw-------  1 toshiaki  staff  1708 Apr 10 02:14 server.key

Use this certificate to make PostgreSQL TLS-enabled. Run the following command.

docker run --rm \
 -p 5432:5432 \
 -e POSTGRES_DB=vehicle \
 -e POSTGRES_USER=vehicle \
 -e POSTGRES_PASSWORD=vehicle \
 -e POSTGRESQL_ENABLE_TLS=yes \
 -e POSTGRESQL_TLS_CERT_FILE=/certs/server.crt \
 -e POSTGRESQL_TLS_KEY_FILE=/certs/server.key \
 -e POSTGRESQL_TLS_CA_FILE=/certs/root.crt \
 -e POSTGRESQL_PGHBA_REMOVE_FILTERS=hostssl \
 -v ${PWD}/certs:/certs \
 bitnami/postgresql:11.11.0-debian-10-r59

First, connect to PostgreSQL with TLS without Binding. Run the following command. Since sslMode is verify-full, the server you connect to must be trusted.

docker run --rm \
 -p 8080:8080 \
 -m 1g \
 -e SPRING_DATASOURCE_HIKARI_DATASOURCEPROPERTIES_SSLMODE=verify-full \
 -e SPRING_DATASOURCE_HIKARI_DATASOURCEPROPERTIES_SSLFACTORY=org.postgresql.ssl.DefaultJavaSSLFactory \
 -e SERVICE_BINDING_ROOT=/bindings \
 -v ${PWD}/bindings:/bindings \
 docker.io/library/vehicle-api:0.0.1-SNAPSHOT

Since the self-signed certificate is not trusted, the error message "unable to find valid certification path to requested target" is output as shown in the following log.

Setting Active Processor Count to 4
Calculated JVM Memory Configuration: -XX:MaxDirectMemorySize=10M -Xmx449112K -XX:MaxMetaspaceSize=87463K -XX:ReservedCodeCacheSize=240M -Xss1M (Total Memory: 1G, Thread Count: 250, Loaded Class Count: 13028, Headroom: 0%)
Adding 129 container CA certificates to JVM truststore
Spring Cloud Bindings Enabled
Picked up JAVA_TOOL_OPTIONS: -Djava.security.properties=/layers/paketo-buildpacks_bellsoft-liberica/java-security-properties/java-security.properties -agentpath:/layers/paketo-buildpacks_bellsoft-liberica/jvmkill/jvmkill-1.16.0-RELEASE.so=printHeapHistogram=1 -XX:ActiveProcessorCount=4 -XX:MaxDirectMemorySize=10M -Xmx449112K -XX:MaxMetaspaceSize=87463K -XX:ReservedCodeCacheSize=240M -Xss1M -Dorg.springframework.cloud.bindings.boot.enable=true

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v2.4.4)

2021-04-10 09:50:49.865  INFO 1 --- [           main] com.example.VehicleApiApplication        : Starting VehicleApiApplication v0.0.1-SNAPSHOT using Java 11.0.10 on 74e090162265 with PID 1 (/workspace/BOOT-INF/classes started by cnb in /workspace)
2021-04-10 09:50:49.868  INFO 1 --- [           main] com.example.VehicleApiApplication        : No active profile set, falling back to default profiles: default
2021-04-10 09:50:49.917  INFO 1 --- [           main] .BindingSpecificEnvironmentPostProcessor : Creating binding-specific PropertySource from Kubernetes Service Bindings
2021-04-10 09:50:50.881  INFO 1 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2021-04-10 09:50:50.891  INFO 1 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2021-04-10 09:50:50.892  INFO 1 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.44]
2021-04-10 09:50:50.938  INFO 1 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2021-04-10 09:50:50.938  INFO 1 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1020 ms
2021-04-10 09:50:51.142  INFO 1 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
2021-04-10 09:50:52.426 ERROR 1 --- [           main] com.zaxxer.hikari.pool.HikariPool        : HikariPool-1 - Exception during pool initialization.

org.postgresql.util.PSQLException: SSL error: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
        at org.postgresql.ssl.MakeSSL.convert(MakeSSL.java:43) ~[postgresql-42.2.19.jar:42.2.19]
        at org.postgresql.core.v3.ConnectionFactoryImpl.enableSSL(ConnectionFactoryImpl.java:534) ~[postgresql-42.2.19.jar:42.2.19]
        at org.postgresql.core.v3.ConnectionFactoryImpl.tryConnect(ConnectionFactoryImpl.java:149) ~[postgresql-42.2.19.jar:42.2.19]
        at org.postgresql.core.v3.ConnectionFactoryImpl.openConnectionImpl(ConnectionFactoryImpl.java:213) ~[postgresql-42.2.19.jar:42.2.19]
        at org.postgresql.core.ConnectionFactory.openConnection(ConnectionFactory.java:51) ~[postgresql-42.2.19.jar:42.2.19]
        at org.postgresql.jdbc.PgConnection.<init>(PgConnection.java:223) ~[postgresql-42.2.19.jar:42.2.19]
        at org.postgresql.Driver.makeConnection(Driver.java:465) ~[postgresql-42.2.19.jar:42.2.19]
        at org.postgresql.Driver.connect(Driver.java:264) ~[postgresql-42.2.19.jar:42.2.19]
        at com.zaxxer.hikari.util.DriverDataSource.getConnection(DriverDataSource.java:138) ~[HikariCP-3.4.5.jar:na]
...

Then create a Binding to trust this CA certificate. Execute the following command. Match the specifications of the CA Certificates Buildpack.

mkdir -p bindings/trusted-certs
echo ca-certificates > bindings/trusted-certs/type
cp certs/root.crt bindings/trusted-certs/

The file structure is as follows.

$ tree bindings 
bindings
|-- trusted-certs
|   |-- root.crt
|   `-- type
`-- vehicle-db
    |-- database
    |-- host
    |-- password
    |-- port
    |-- type
    `-- username

2 directories, 8 files

Execute the following command again.

docker run --rm \
 -p 8080:8080 \
 -m 1g \
 -e SPRING_DATASOURCE_HIKARI_DATASOURCEPROPERTIES_SSLMODE=verify-full \
 -e SPRING_DATASOURCE_HIKARI_DATASOURCEPROPERTIES_SSLFACTORY=org.postgresql.ssl.DefaultJavaSSLFactory \
 -e SERVICE_BINDING_ROOT=/bindings \
 -v ${PWD}/bindings:/bindings \
 docker.io/library/vehicle-api:0.0.1-SNAPSHOT

The following log is output.

Added 1 additional CA certificate(s) to system truststore
Setting Active Processor Count to 4
Calculated JVM Memory Configuration: -XX:MaxDirectMemorySize=10M -Xmx449112K -XX:MaxMetaspaceSize=87463K -XX:ReservedCodeCacheSize=240M -Xss1M (Total Memory: 1G, Thread Count: 250, Loaded Class Count: 13028, Headroom: 0%)
Adding 130 container CA certificates to JVM truststore
Spring Cloud Bindings Enabled
Picked up JAVA_TOOL_OPTIONS: -Djava.security.properties=/layers/paketo-buildpacks_bellsoft-liberica/java-security-properties/java-security.properties -agentpath:/layers/paketo-buildpacks_bellsoft-liberica/jvmkill/jvmkill-1.16.0-RELEASE.so=printHeapHistogram=1 -XX:ActiveProcessorCount=4 -XX:MaxDirectMemorySize=10M -Xmx449112K -XX:MaxMetaspaceSize=87463K -XX:ReservedCodeCacheSize=240M -Xss1M -Dorg.springframework.cloud.bindings.boot.enable=true

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v2.4.4)

2021-04-10 09:52:56.993  INFO 1 --- [           main] com.example.VehicleApiApplication        : Starting VehicleApiApplication v0.0.1-SNAPSHOT using Java 11.0.10 on b46e4ab3e521 with PID 1 (/workspace/BOOT-INF/classes started by cnb in /workspace)
2021-04-10 09:52:56.996  INFO 1 --- [           main] com.example.VehicleApiApplication        : No active profile set, falling back to default profiles: default
2021-04-10 09:52:57.046  INFO 1 --- [           main] .BindingSpecificEnvironmentPostProcessor : Creating binding-specific PropertySource from Kubernetes Service Bindings
2021-04-10 09:52:58.047  INFO 1 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2021-04-10 09:52:58.056  INFO 1 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2021-04-10 09:52:58.057  INFO 1 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.44]
2021-04-10 09:52:58.098  INFO 1 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2021-04-10 09:52:58.098  INFO 1 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1051 ms
2021-04-10 09:52:58.307  INFO 1 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
2021-04-10 09:52:58.640  INFO 1 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
2021-04-10 09:52:58.815  INFO 1 --- [           main] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'
2021-04-10 09:52:59.109  INFO 1 --- [           main] o.s.b.a.e.web.EndpointLinksResolver      : Exposing 2 endpoint(s) beneath base path '/actuator'
2021-04-10 09:52:59.186  INFO 1 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2021-04-10 09:52:59.197  INFO 1 --- [           main] com.example.VehicleApiApplication        : Started VehicleApiApplication in 2.622 seconds (JVM running for 2.97)

2021-04-09 17:25:30.410  INFO 1 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1009 ms
2021-04-09 17:25:30.608  INFO 1 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
2021-04-09 17:25:30.941  INFO 1 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
2021-04-09 17:25:31.097  INFO 1 --- [           main] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'
2021-04-09 17:25:31.429  INFO 1 --- [           main] o.s.b.a.e.web.EndpointLinksResolver      : Exposing 2 endpoint(s) beneath base path '/actuator'
2021-04-09 17:25:31.474  INFO 1 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2021-04-09 17:25:31.487  INFO 1 --- [           main] com.example.VehicleApiApplication        : Started VehicleApiApplication in 2.493 seconds (JVM running for 2.79)

Previously, the message Adding 129 container CA certificates to JVM truststore was output, but this time the message Adding 130 container CA certificates to JVM truststore was output, indicating that one CA certificate was added to the Truststore. The application also started and successfully connected to PostgreSQL with sslMode set to verify-full.

In Java Buildpack of Paketo Buildpack, if you specify the directory of the CA certificate you want to add to the environment variable SSL_CERT_DIR, the certificate in that directory will be added to the Java Truststore at startup. CA certificate Buildpack will add the value to the environment variable SSL_CERT_DIR if there is a Binding whose type is ca-certificates.

The environment variables related to Service Binding that are set in the application when Bind Service of PostgreSQL and Service Bind of CA Certificates have not changed. Just add more information to bind, and this information will be automatically injected into your application.

GraalVM allows you to compile your Java application into a native image, which makes your application launch much faster than the JVM.

By using Spring Native, you can greatly omit the work required when the Spring Boot application is converted to a native image. Let's make vehicle-api into a native image with GraalVM.

You can use Paketo GraalVM Buildpack to create a container image of a native image without installing GraalVM on your laptop.

Update your application's pom.xml to use Spring Native. Execute the following command.

curl -s https://start.spring.io/pom.xml \
 -d artifactId=vehicle-api \
 -d baseDir=vehicle-api \
 -d dependencies=web,jdbc,postgresql,actuator,native \
 -d packageName=com.example >pom.xml

The spring-native library will be added and the settings for using paketobuildpacks/builder:tiny will be updated.

Run the following command.

./mvnw clean spring-boot:build-image -Dmaven.test.skip=true

The following log will be output. You can see that GraalVM has been downloaded and is in the process of creating a native image.

The build time will be 5 minutes or more, depending on the Spec of the execution environment.

[INFO] Scanning for projects...
...
[INFO] 
[INFO] <<< spring-boot-maven-plugin:2.4.5-SNAPSHOT:build-image (default-cli) < package @ vehicle-api <<<
[INFO] 
[INFO] 
[INFO] --- spring-boot-maven-plugin:2.4.5-SNAPSHOT:build-image (default-cli) @ vehicle-api ---
[INFO] Building image 'docker.io/library/vehicle-api:0.0.1-SNAPSHOT'
[INFO] 
[INFO]  > Pulling builder image 'docker.io/paketobuildpacks/builder:tiny' 100%
[INFO]  > Pulled builder image 'paketobuildpacks/builder@sha256:e1b1c8f5e76b266924aae21d93dd14e351145b5106f39ba71b2cdbb2a5bf3758'
[INFO]  > Pulling run image 'docker.io/paketobuildpacks/run:tiny-cnb' 100%
[INFO]  > Pulled run image 'paketobuildpacks/run@sha256:1ab5cd68c56bceecb3127a75bc3cb167cbc7f117f1808f02c41f93d7a84e2b92'
[INFO]  > Executing lifecycle version v0.11.1
[INFO]  > Using build cache volume 'pack-cache-13a8f10fbd06.build'
[INFO] 
[INFO]  > Running creator
[INFO]     [creator]     ===> DETECTING
[INFO]     [creator]     4 of 11 buildpacks participating
[INFO]     [creator]     paketo-buildpacks/graalvm        6.0.0
[INFO]     [creator]     paketo-buildpacks/executable-jar 5.0.0
[INFO]     [creator]     paketo-buildpacks/spring-boot    4.2.0
[INFO]     [creator]     paketo-buildpacks/native-image   4.0.0
[INFO]     [creator]     ===> ANALYZING
[INFO]     [creator]     Restoring metadata for "paketo-buildpacks/spring-boot:helper" from app image
[INFO]     [creator]     Restoring metadata for "paketo-buildpacks/spring-boot:spring-cloud-bindings" from app image
[INFO]     [creator]     Restoring metadata for "paketo-buildpacks/spring-boot:web-application-type" from app image
[INFO]     [creator]     ===> RESTORING
[INFO]     [creator]     ===> BUILDING
[INFO]     [creator]     
[INFO]     [creator]     Paketo GraalVM Buildpack 6.0.0
[INFO]     [creator]       https://github.com/paketo-buildpacks/graalvm
[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]       GraalVM JDK 11.0.10: Contributing to layer
[INFO]     [creator]         Downloading from https://github.com/graalvm/graalvm-ce-builds/releases/download/vm-21.0.0.2/graalvm-ce-java11-linux-amd64-21.0.0.2.tar.gz
[INFO]     [creator]         Verifying checksum
[INFO]     [creator]         Expanding to /layers/paketo-buildpacks_graalvm/jdk
[INFO]     [creator]         Adding 129 container CA certificates to JVM truststore
[INFO]     [creator]       GraalVM Native Image Substrate VM 11.0.10
[INFO]     [creator]         Downloading from https://github.com/graalvm/graalvm-ce-builds/releases/download/vm-21.0.0.2/native-image-installable-svm-java11-linux-amd64-21.0.0.2.jar
[INFO]     [creator]         Verifying checksum
[INFO]     [creator]         Installing substrate VM
[INFO]     [creator]     Processing Component archive: /tmp/24e5a08e2714aee343b22c266285090721ff882ab0a31b7e8e4a68585c38f421/native-image-installable-svm-java11-linux-amd64-21.0.0.2.jar
[INFO]     [creator]     Installing new component: Native Image (org.graalvm.native-image, version 21.0.0.2)
[INFO]     [creator]         Writing env.build/JAVA_HOME.override
[INFO]     [creator]         Writing env.build/JDK_HOME.override
[INFO]     [creator]     
[INFO]     [creator]     Paketo Executable JAR Buildpack 5.0.0
[INFO]     [creator]       https://github.com/paketo-buildpacks/executable-jar
[INFO]     [creator]       Class Path: Contributing to layer
[INFO]     [creator]         Writing env.build/CLASSPATH.delim
[INFO]     [creator]         Writing env.build/CLASSPATH.prepend
[INFO]     [creator]     
[INFO]     [creator]     Paketo Spring Boot Buildpack 4.2.0
[INFO]     [creator]       https://github.com/paketo-buildpacks/spring-boot
[INFO]     [creator]       Class Path: Contributing to layer
[INFO]     [creator]         Writing env.build/CLASSPATH.append
[INFO]     [creator]         Writing env.build/CLASSPATH.delim
[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]     
[INFO]     [creator]     Paketo Native Image Buildpack 4.0.0
[INFO]     [creator]       https://github.com/paketo-buildpacks/native-image
[INFO]     [creator]       Build Configuration:
[INFO]     [creator]         $BP_NATIVE_IMAGE                  true  enable native image build
[INFO]     [creator]         $BP_NATIVE_IMAGE_BUILD_ARGUMENTS        arguments to pass to the native-image command
[INFO]     [creator]       Native Image: Contributing to layer
[INFO]     [creator]         GraalVM Version 21.0.0.2 (Java Version 11.0.10+8-jvmci-21.0-b06)
[INFO]     [creator]         Executing native-image -H:+StaticExecutableWithDynamicLibC -H:Name=/layers/paketo-buildpacks_native-image/native-image/com.example.VehicleApiApplication -cp /workspace:/workspace/BOOT-INF/classes:/workspace/BOOT-INF/lib/spring-boot-2.4.5-SNAPSHOT.jar:/workspace/BOOT-INF/lib/spring-boot-autoconfigure-2.4.5-SNAPSHOT.jar:/workspace/BOOT-INF/lib/logback-classic-1.2.3.jar:/workspace/BOOT-INF/lib/logback-core-1.2.3.jar:/workspace/BOOT-INF/lib/log4j-to-slf4j-2.13.3.jar:/workspace/BOOT-INF/lib/log4j-api-2.13.3.jar:/workspace/BOOT-INF/lib/jul-to-slf4j-1.7.30.jar:/workspace/BOOT-INF/lib/jakarta.annotation-api-1.3.5.jar:/workspace/BOOT-INF/lib/snakeyaml-1.27.jar:/workspace/BOOT-INF/lib/spring-boot-actuator-autoconfigure-2.4.5-SNAPSHOT.jar:/workspace/BOOT-INF/lib/spring-boot-actuator-2.4.5-SNAPSHOT.jar:/workspace/BOOT-INF/lib/jackson-databind-2.11.4.jar:/workspace/BOOT-INF/lib/jackson-annotations-2.11.4.jar:/workspace/BOOT-INF/lib/jackson-core-2.11.4.jar:/workspace/BOOT-INF/lib/jackson-datatype-jsr310-2.11.4.jar:/workspace/BOOT-INF/lib/micrometer-core-1.6.5.jar:/workspace/BOOT-INF/lib/HdrHistogram-2.1.12.jar:/workspace/BOOT-INF/lib/LatencyUtils-2.0.3.jar:/workspace/BOOT-INF/lib/HikariCP-3.4.5.jar:/workspace/BOOT-INF/lib/slf4j-api-1.7.30.jar:/workspace/BOOT-INF/lib/spring-jdbc-5.3.6-SNAPSHOT.jar:/workspace/BOOT-INF/lib/spring-beans-5.3.6-SNAPSHOT.jar:/workspace/BOOT-INF/lib/spring-tx-5.3.6-SNAPSHOT.jar:/workspace/BOOT-INF/lib/jackson-datatype-jdk8-2.11.4.jar:/workspace/BOOT-INF/lib/jackson-module-parameter-names-2.11.4.jar:/workspace/BOOT-INF/lib/tomcat-embed-core-9.0.44.jar:/workspace/BOOT-INF/lib/jakarta.el-3.0.3.jar:/workspace/BOOT-INF/lib/tomcat-embed-websocket-9.0.44.jar:/workspace/BOOT-INF/lib/spring-web-5.3.6-SNAPSHOT.jar:/workspace/BOOT-INF/lib/spring-webmvc-5.3.6-SNAPSHOT.jar:/workspace/BOOT-INF/lib/spring-aop-5.3.6-SNAPSHOT.jar:/workspace/BOOT-INF/lib/spring-context-5.3.6-SNAPSHOT.jar:/workspace/BOOT-INF/lib/spring-expression-5.3.6-SNAPSHOT.jar:/workspace/BOOT-INF/lib/spring-native-0.9.2-SNAPSHOT.jar:/workspace/BOOT-INF/lib/postgresql-42.2.19.jar:/workspace/BOOT-INF/lib/checker-qual-3.5.0.jar:/workspace/BOOT-INF/lib/spring-core-5.3.6-SNAPSHOT.jar:/workspace/BOOT-INF/lib/spring-jcl-5.3.6-SNAPSHOT.jar:/workspace/BOOT-INF/lib/spring-boot-jarmode-layertools-2.4.5-SNAPSHOT.jar com.example.VehicleApiApplication
[INFO]     [creator]     [/layers/paketo-buildpacks_native-image/native-image/com.example.VehicleApiApplication:159]    classlist:   3,797.93 ms,  0.96 GB
[INFO]     [creator]     Warning: class initialization of class org.springframework.boot.validation.MessageInterpolatorFactory failed with exception java.lang.NoClassDefFoundError: javax/validation/ValidationException. This class will be initialized at run time because option --allow-incomplete-classpath is used for image building. Use the option --initialize-at-run-time=org.springframework.boot.validation.MessageInterpolatorFactory to explicitly request delayed initialization of this class.
[INFO]     [creator]     [/layers/paketo-buildpacks_native-image/native-image/com.example.VehicleApiApplication:159]        (cap):     458.32 ms,  1.19 GB
[INFO]     [creator]     [/layers/paketo-buildpacks_native-image/native-image/com.example.VehicleApiApplication:159]        setup:   1,912.29 ms,  1.19 GB
[INFO]     [creator]     WARNING: Could not register reflection metadata for org.springframework.boot.autoconfigure.jdbc.DataSourceConfiguration$Dbcp2. Reason: java.lang.NoClassDefFoundError: org/apache/commons/dbcp2/BasicDataSource.
...
[INFO]     [creator]     [/layers/paketo-buildpacks_native-image/native-image/com.example.VehicleApiApplication:159]     (clinit):   1,720.26 ms,  4.09 GB
[INFO]     [creator]     [/layers/paketo-buildpacks_native-image/native-image/com.example.VehicleApiApplication:159]   (typeflow):  38,729.68 ms,  4.09 GB
[INFO]     [creator]     [/layers/paketo-buildpacks_native-image/native-image/com.example.VehicleApiApplication:159]    (objects):  29,370.84 ms,  4.09 GB
[INFO]     [creator]     [/layers/paketo-buildpacks_native-image/native-image/com.example.VehicleApiApplication:159]   (features):   4,312.88 ms,  4.09 GB
[INFO]     [creator]     [/layers/paketo-buildpacks_native-image/native-image/com.example.VehicleApiApplication:159]     analysis:  76,526.02 ms,  4.09 GB
[INFO]     [creator]     [/layers/paketo-buildpacks_native-image/native-image/com.example.VehicleApiApplication:159]     universe:   2,836.68 ms,  4.06 GB
[INFO]     [creator]     [/layers/paketo-buildpacks_native-image/native-image/com.example.VehicleApiApplication:159]      (parse):  14,582.05 ms,  4.97 GB
[INFO]     [creator]     [/layers/paketo-buildpacks_native-image/native-image/com.example.VehicleApiApplication:159]     (inline):   9,640.34 ms,  5.72 GB
[INFO]     [creator]     [/layers/paketo-buildpacks_native-image/native-image/com.example.VehicleApiApplication:159]    (compile):  41,254.15 ms,  6.04 GB
[INFO]     [creator]     [/layers/paketo-buildpacks_native-image/native-image/com.example.VehicleApiApplication:159]      compile:  69,025.23 ms,  6.04 GB
[INFO]     [creator]     [/layers/paketo-buildpacks_native-image/native-image/com.example.VehicleApiApplication:159]        image:   7,755.51 ms,  5.95 GB
[INFO]     [creator]     [/layers/paketo-buildpacks_native-image/native-image/com.example.VehicleApiApplication:159]        write:     886.82 ms,  5.95 GB
[INFO]     [creator]     [/layers/paketo-buildpacks_native-image/native-image/com.example.VehicleApiApplication:159]      [total]: 163,358.86 ms,  5.95 GB
[INFO]     [creator]       Removing bytecode
[INFO]     [creator]       Process types:
[INFO]     [creator]         native-image: /workspace/com.example.VehicleApiApplication (direct)
[INFO]     [creator]         task:         /workspace/com.example.VehicleApiApplication (direct)
[INFO]     [creator]         web:          /workspace/com.example.VehicleApiApplication (direct)
[INFO]     [creator]     ===> EXPORTING
[INFO]     [creator]     Adding 1/1 app layer(s)
[INFO]     [creator]     Reusing 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]     Saving docker.io/library/vehicle-api:0.0.1-SNAPSHOT...
[INFO]     [creator]     *** Images (e4e8ed011ce3):
[INFO]     [creator]           docker.io/library/vehicle-api:0.0.1-SNAPSHOT
[INFO]     [creator]     Adding cache layer 'paketo-buildpacks/graalvm:jdk'
[INFO]     [creator]     Adding cache layer 'paketo-buildpacks/native-image:native-image'
[INFO] 
[INFO] Successfully built image 'docker.io/library/vehicle-api:0.0.1-SNAPSHOT'
[INFO] 
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  04:48 min
[INFO] Finished at: 2021-04-11T00:37:47+09:00
[INFO] ------------------------------------------------------------------------

Run the following command to see the image you created. paketobuildpacks/builder:tiny Builder uses the smallest Ubuntu base image, so it is smaller than the image using the JVM.

$ docker images | grep vehicle
vehicle-api                     0.0.1-SNAPSHOT          e4e8ed011ce3   41 years ago   112MB
vehicle-api                     16                      6cc5e40ea762   41 years ago   291MB
vehicle-api                     latest                  048a01061034   41 years ago   266MB

Run the following command. Since the native image consumes less memory than the JVM, we will set the memory size to 150MB here.

docker run --rm \
 -p 8080:8080 \
 -m 150m \
 -e SPRING_DATASOURCE_URL=jdbc:postgresql://host.docker.internal:5432/vehicle \
 docker.io/library/vehicle-api:0.0.1-SNAPSHOT

The following log is output. Make sure it starts up very fast.

2021-04-10 15:58:55.798  INFO 1 --- [           main] o.s.nativex.NativeListener               : This application is bootstrapped with code generated with Spring AOT

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::       (v2.4.5-SNAPSHOT)

2021-04-10 15:58:55.799  INFO 1 --- [           main] com.example.VehicleApiApplication        : Starting VehicleApiApplication using Java 11.0.10 on 5b6ce3d3203e with PID 1 (/workspace/com.example.VehicleApiApplication started by cnb in /workspace)
2021-04-10 15:58:55.800  INFO 1 --- [           main] com.example.VehicleApiApplication        : No active profile set, falling back to default profiles: default
2021-04-10 15:58:55.848  INFO 1 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
Apr 10, 2021 3:58:55 PM org.apache.coyote.AbstractProtocol init
INFO: Initializing ProtocolHandler ["http-nio-8080"]
Apr 10, 2021 3:58:55 PM org.apache.catalina.core.StandardService startInternal
INFO: Starting service [Tomcat]
Apr 10, 2021 3:58:55 PM org.apache.catalina.core.StandardEngine startInternal
INFO: Starting Servlet engine: [Apache Tomcat/9.0.44]
2021-04-10 15:58:55.850  INFO 1 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 50 ms
Apr 10, 2021 3:58:55 PM org.apache.catalina.core.ApplicationContext log
INFO: Initializing Spring embedded WebApplicationContext
2021-04-10 15:58:55.853  WARN 1 --- [           main] i.m.c.i.binder.jvm.JvmGcMetrics          : GC notifications will not be available because MemoryPoolMXBeans are not provided by the JVM
2021-04-10 15:58:55.865  INFO 1 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
2021-04-10 15:58:55.875  INFO 1 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
2021-04-10 15:58:55.895  INFO 1 --- [           main] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'
2021-04-10 15:58:55.910  INFO 1 --- [           main] o.s.b.a.e.web.EndpointLinksResolver      : Exposing 2 endpoint(s) beneath base path '/actuator'
Apr 10, 2021 3:58:55 PM org.apache.coyote.AbstractProtocol start
INFO: Starting ProtocolHandler ["http-nio-8080"]
2021-04-10 15:58:55.916  INFO 1 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2021-04-10 15:58:55.917  INFO 1 --- [           main] com.example.VehicleApiApplication        : Started VehicleApiApplication in 0.123 seconds (JVM running for 0.125)

Next, communicate with PostgreSQL via TLS. To use TLS, you need to add --enable-all-security-services to GraalVM's native-image option. In Spring Native, you can specify the option of native-image with @NativeHint annotation, so edit src/main/java/com/example/VehicleApplication.java as follows.

package com.example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.nativex.hint.NativeHint;

@SpringBootApplication
@NativeHint(options = { "--enable-all-security-services" })
public class VehicleApiApplication {

  public static void main(String[] args) {
     SpringApplication.run(VehicleApiApplication.class, args);
  }

}

Execute the following command again.

./mvnw clean spring-boot:build-image -Dmaven.test.skip=true

When creating a native image with Buildpack, the Buildpack functions and Service Binding introduced so far cannot be used at the time of writing. Here, let's use the function of PostgreSQL JDBC Driver instead for setting the CA certificate. Change sslFactory to org.postgresql.ssl.LibPQFactory (default) and place the CA certificate in $HOME/.postgreql/root.crt

Run the following command.

docker run --rm \
 -p 8080:8080 \
 -m 150m \
 -e SPRING_DATASOURCE_URL=jdbc:postgresql://host.docker.internal:5432/vehicle \
 -e SPRING_DATASOURCE_HIKARI_DATASOURCEPROPERTIES_SSLMODE=verify-full \
 -e SPRING_DATASOURCE_HIKARI_DATASOURCEPROPERTIES_SSLFACTORY=org.postgresql.ssl.LibPQFactory \
 -v ${PWD}/certs/root.crt:/home/cnb/.postgresql/root.crt \
 docker.io/library/vehicle-api:0.0.1-SNAPSHOT

The following log is output.

2021-04-10 16:37:08.722  INFO 1 --- [           main] o.s.nativex.NativeListener               : This application is bootstrapped with code generated with Spring AOT

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::       (v2.4.5-SNAPSHOT)

2021-04-10 16:37:08.723  INFO 1 --- [           main] com.example.VehicleApiApplication        : Starting VehicleApiApplication using Java 11.0.10 on 98e28fb9c99c with PID 1 (/workspace/com.example.VehicleApiApplication started by cnb in /workspace)
2021-04-10 16:37:08.723  INFO 1 --- [           main] com.example.VehicleApiApplication        : No active profile set, falling back to default profiles: default
2021-04-10 16:37:08.778  INFO 1 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
Apr 10, 2021 4:37:08 PM org.apache.coyote.AbstractProtocol init
INFO: Initializing ProtocolHandler ["http-nio-8080"]
Apr 10, 2021 4:37:08 PM org.apache.catalina.core.StandardService startInternal
INFO: Starting service [Tomcat]
Apr 10, 2021 4:37:08 PM org.apache.catalina.core.StandardEngine startInternal
INFO: Starting Servlet engine: [Apache Tomcat/9.0.44]
2021-04-10 16:37:08.779  INFO 1 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 55 ms
Apr 10, 2021 4:37:08 PM org.apache.catalina.core.ApplicationContext log
INFO: Initializing Spring embedded WebApplicationContext
2021-04-10 16:37:08.782  WARN 1 --- [           main] i.m.c.i.binder.jvm.JvmGcMetrics          : GC notifications will not be available because MemoryPoolMXBeans are not provided by the JVM
2021-04-10 16:37:08.795  INFO 1 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
2021-04-10 16:37:08.824  INFO 1 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
2021-04-10 16:37:08.843  INFO 1 --- [           main] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'
2021-04-10 16:37:08.859  INFO 1 --- [           main] o.s.b.a.e.web.EndpointLinksResolver      : Exposing 2 endpoint(s) beneath base path '/actuator'
Apr 10, 2021 4:37:08 PM org.apache.coyote.AbstractProtocol start
INFO: Starting ProtocolHandler ["http-nio-8080"]
2021-04-10 16:37:08.865  INFO 1 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2021-04-10 16:37:08.866  INFO 1 --- [           main] com.example.VehicleApiApplication        : Started VehicleApiApplication in 0.148 seconds (JVM running for 0.15)

Make sure you can access the application.

When creating a container image of a Java application with the Builder of Paketo Buildpacks, Paketo BellSoft Liberica Buildpack is used by default, and the JRE of Bellsoft Liberica is included. Here are some ways to use other distributions.

Adopt OpenJDK

You can use Adopt OpenJDK by using Paketo AdoptOpenJDK Buildpack. Instead of using the set of Buildpacks provided by the Builder, you can replace it by passing all the Buildpacks you want to use to the pack CLI.

Run the following command.

pack build vehicle-api:adopt-openjdk \
 --builder paketobuildpacks/builder:base \
 --buildpack paketo-buildpacks/ca-certificates \
 --buildpack paketo-buildpacks/adopt-openjdk \
 --buildpack paketo-buildpacks/maven \
 --buildpack paketo-buildpacks/executable-jar \
 --buildpack paketo-buildpacks/spring-boot

The following log is output. You can see that Adopt OpenJDK has been downloaded.

base: Pulling from paketobuildpacks/builder
Digest: sha256:7f2b74ba47b235e5a30393ad83c3e3c12ff5229c190faffd67d842e064b7743e
Status: Image is up to date for paketobuildpacks/builder:base
base-cnb: Pulling from paketobuildpacks/run
Digest: sha256:aa9a8a0a4bb607422d3ff59923638f2a6cf3d1e6b404aee9ae027127ea88a5c1
Status: Image is up to date for paketobuildpacks/run:base-cnb
gcr.io/paketo-buildpacks/adopt-openjdk@sha256:9fd288108e7b34bea89986446966f885bbf08e7dd80d07277ff3b229549fb6da: Pulling from paketo-buildpacks/adopt-openjdk
Digest: sha256:9fd288108e7b34bea89986446966f885bbf08e7dd80d07277ff3b229549fb6da
Status: Image is up to date for gcr.io/paketo-buildpacks/adopt-openjdk@sha256:9fd288108e7b34bea89986446966f885bbf08e7dd80d07277ff3b229549fb6da
===> DETECTING
paketo-buildpacks/ca-certificates 2.1.0
paketo-buildpacks/adopt-openjdk   7.1.0
paketo-buildpacks/maven           5.0.0
paketo-buildpacks/executable-jar  5.0.0
paketo-buildpacks/spring-boot     4.2.0
===> ANALYZING
Previous image with name "vehicle-api:adopt-openjdk" not found
===> RESTORING
===> BUILDING

Paketo CA Certificates Buildpack 2.1.0
  https://github.com/paketo-buildpacks/ca-certificates
  Launch Helper: Contributing to layer
    Creating /layers/paketo-buildpacks_ca-certificates/helper/exec.d/ca-certificates-helper

Paketo AdoptOpenJDK Buildpack 7.1.0
  https://github.com/paketo-buildpacks/adopt-openjdk
  Build Configuration:
    $BP_JVM_VERSION              11              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
  AdoptOpenJDK JDK 11.0.10: Contributing to layer
    Downloading from https://github.com/AdoptOpenJDK/openjdk11-binaries/releases/download/jdk-11.0.10%2B9/OpenJDK11U-jdk_x64_linux_hotspot_11.0.10_9.tar.gz
    Verifying checksum
    Expanding to /layers/paketo-buildpacks_adopt-openjdk/jdk
    Adding 129 container CA certificates to JVM truststore
    Writing env.build/JAVA_HOME.override
    Writing env.build/JDK_HOME.override
  AdoptOpenJDK JRE 11.0.10: Contributing to layer
    Downloading from https://github.com/AdoptOpenJDK/openjdk11-binaries/releases/download/jdk-11.0.10%2B9/OpenJDK11U-jre_x64_linux_hotspot_11.0.10_9.tar.gz


    Verifying checksum
    Expanding to /layers/paketo-buildpacks_adopt-openjdk/jre
    Adding 129 container CA certificates to JVM truststore
    Writing env.launch/BPI_APPLICATION_PATH.default
    Writing env.launch/BPI_JVM_CACERTS.default
    Writing env.launch/BPI_JVM_CLASS_COUNT.default
    Writing env.launch/BPI_JVM_SECURITY_PROVIDERS.default
    Writing env.launch/JAVA_HOME.default
    Writing env.launch/MALLOC_ARENA_MAX.default
  Launch Helper: Contributing to layer
    Creating /layers/paketo-buildpacks_adopt-openjdk/helper/exec.d/active-processor-count
    Creating /layers/paketo-buildpacks_adopt-openjdk/helper/exec.d/java-opts
    Creating /layers/paketo-buildpacks_adopt-openjdk/helper/exec.d/link-local-dns
    Creating /layers/paketo-buildpacks_adopt-openjdk/helper/exec.d/memory-calculator
    Creating /layers/paketo-buildpacks_adopt-openjdk/helper/exec.d/openssl-certificate-loader
    Creating /layers/paketo-buildpacks_adopt-openjdk/helper/exec.d/security-providers-configurer
    Creating /layers/paketo-buildpacks_adopt-openjdk/helper/exec.d/security-providers-classpath-9
  JVMKill Agent 1.16.0: Contributing to layer
    Downloading from https://github.com/cloudfoundry/jvmkill/releases/download/v1.16.0.RELEASE/jvmkill-1.16.0-RELEASE.so
    Verifying checksum
    Copying to /layers/paketo-buildpacks_adopt-openjdk/jvmkill
    Writing env.launch/JAVA_TOOL_OPTIONS.append
    Writing env.launch/JAVA_TOOL_OPTIONS.delim
  Java Security Properties: Contributing to layer
    Writing env.launch/JAVA_SECURITY_PROPERTIES.default
    Writing env.launch/JAVA_TOOL_OPTIONS.append
    Writing env.launch/JAVA_TOOL_OPTIONS.delim
...
Saving vehicle-api:adopt-openjdk...
*** Images (d3ae69cce52e):
      vehicle-api:adopt-openjdk
Adding cache layer 'paketo-buildpacks/adopt-openjdk:jdk'
Adding cache layer 'paketo-buildpacks/maven:application'
Adding cache layer 'paketo-buildpacks/maven:cache'
Successfully built image vehicle-api:adopt-openjdk

Run the following command.

docker run --rm -p 8080:8080 \
 -m 1g \
 -e MANAGEMENT_ENDPOINTS_WEB_EXPOSURE_INCLUDE="*" \
 -e SPRING_DATASOURCE_URL=jdbc:postgresql://host.docker.internal:5432/vehicle \
 vehicle-api:adopt-openjdk

The following log is output. You can see that Adopt OpenJDK is used.

Calculated JVM Memory Configuration: -XX:MaxDirectMemorySize=10M -Xmx448954K -XX:MaxMetaspaceSize=87621K -XX:ReservedCodeCacheSize=240M -Xss1M (Total Memory: 1G, Thread Count: 250, Loaded Class Count: 13056, Headroom: 0%)
Adding 129 container CA certificates to JVM truststore
Spring Cloud Bindings Enabled
Picked up JAVA_TOOL_OPTIONS: -Djava.security.properties=/layers/paketo-buildpacks_adopt-openjdk/java-security-properties/java-security.properties -agentpath:/layers/paketo-buildpacks_adopt-openjdk/jvmkill/jvmkill-1.16.0-RELEASE.so=printHeapHistogram=1 -XX:ActiveProcessorCount=4 -XX:MaxDirectMemorySize=10M -Xmx448954K -XX:MaxMetaspaceSize=87621K -XX:ReservedCodeCacheSize=240M -Xss1M -Dorg.springframework.cloud.bindings.boot.enable=true
...

Run the following command. You can check the vendor name and version of Java that is actually running.

$ curl -s http://localhost:8080/actuator/env | jq ".propertySources[] | select(.name == \"systemProperties\").properties[\"java.vendor.version\"]"
{
  "value": "AdoptOpenJDK"
}

$ curl -s http://localhost:8080/actuator/env | jq ".propertySources[] | select(.name == \"systemProperties\").properties[\"java.vm.version\"]"
{
  "value": "11.0.10+9"
}

Azul Zulu

You can use Azul Zulu by using Paketo Azul Zulu Buildpack. It can be used with the following command as in the case of Adopt OpenJDK.

pack build vehicle-api:azul \
 --builder paketobuildpacks/builder:base \
 --buildpack paketo-buildpacks/ca-certificates \
 --buildpack paketo-buildpacks/azul-zulu \
 --buildpack paketo-buildpacks/maven \
 --buildpack paketo-buildpacks/executable-jar \
 --buildpack paketo-buildpacks/spring-boot

In the case of Azul Zulu, it is included in the Paketo Azure Java Buildpack, a composite Buildpack, which is a collection of Buildpacks. This Buildpack defines a set of Buildpacks that are supposed to be used on Azure, so you don't have to specify each Buildpack one by one.

Run the following command.

pack build vehicle-api:azul \
 --builder paketobuildpacks/builder:base \
 --buildpack paketo-buildpacks/java-azure

The following log is output. You can see that Azul Zulu has been downloaded.

base: Pulling from paketobuildpacks/builder
Digest: sha256:7f2b74ba47b235e5a30393ad83c3e3c12ff5229c190faffd67d842e064b7743e
Status: Image is up to date for paketobuildpacks/builder:base
base-cnb: Pulling from paketobuildpacks/run
Digest: sha256:aa9a8a0a4bb607422d3ff59923638f2a6cf3d1e6b404aee9ae027127ea88a5c1
Status: Image is up to date for paketobuildpacks/run:base-cnb
gcr.io/paketo-buildpacks/java-azure@sha256:4109b133b548c4eea85c84a082d64da64609714d105e13b5fdae57689d693f5c: Pulling from paketo-buildpacks/java-azure
Digest: sha256:4109b133b548c4eea85c84a082d64da64609714d105e13b5fdae57689d693f5c
Status: Downloaded newer image for gcr.io/paketo-buildpacks/java-azure@sha256:4109b133b548c4eea85c84a082d64da64609714d105e13b5fdae57689d693f5c
===> DETECTING
7 of 17 buildpacks participating
paketo-buildpacks/ca-certificates 2.1.0
paketo-buildpacks/azul-zulu       7.1.0
paketo-buildpacks/maven           5.0.0
paketo-buildpacks/executable-jar  5.0.0
paketo-buildpacks/apache-tomcat   5.1.0
paketo-buildpacks/dist-zip        4.0.0
paketo-buildpacks/spring-boot     4.2.0
===> ANALYZING
Previous image with name "vehicle-api:azul" not found
===> RESTORING
===> BUILDING

Paketo CA Certificates Buildpack 2.1.0
  https://github.com/paketo-buildpacks/ca-certificates
  Launch Helper: Contributing to layer
    Creating /layers/paketo-buildpacks_ca-certificates/helper/exec.d/ca-certificates-helper

Paketo Azul Zulu Buildpack 7.1.0
  https://github.com/paketo-buildpacks/azul-zulu
  Build Configuration:
    $BP_JVM_VERSION              11              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
  Azul Zulu JDK 11.0.10: Contributing to layer
    Downloading from https://repos.azul.com/azure-only/zulu/packages/zulu-11/11.0.10/zulu-11-azure-jdk_11.45.27-11.0.10-linux_x64.tar.gz
    Verifying checksum
    Expanding to /layers/paketo-buildpacks_azul-zulu/jdk
    Adding 129 container CA certificates to JVM truststore
    Writing env.build/JAVA_HOME.override
    Writing env.build/JDK_HOME.override
  Azul Zulu JRE 11.0.10: Contributing to layer
    Downloading from https://repos.azul.com/azure-only/zulu/packages/zulu-11/11.0.10/zulu-11-azure-jre_11.45.27-11.0.10-linux_x64.tar.gz
    Verifying checksum
    Expanding to /layers/paketo-buildpacks_azul-zulu/jre
    Adding 129 container CA certificates to JVM truststore
    Writing env.launch/BPI_APPLICATION_PATH.default
    Writing env.launch/BPI_JVM_CACERTS.default
    Writing env.launch/BPI_JVM_CLASS_COUNT.default
    Writing env.launch/BPI_JVM_SECURITY_PROVIDERS.default
    Writing env.launch/JAVA_HOME.default
    Writing env.launch/MALLOC_ARENA_MAX.default
  Launch Helper: Contributing to layer
    Creating /layers/paketo-buildpacks_azul-zulu/helper/exec.d/active-processor-count
    Creating /layers/paketo-buildpacks_azul-zulu/helper/exec.d/java-opts
    Creating /layers/paketo-buildpacks_azul-zulu/helper/exec.d/link-local-dns
    Creating /layers/paketo-buildpacks_azul-zulu/helper/exec.d/memory-calculator
    Creating /layers/paketo-buildpacks_azul-zulu/helper/exec.d/openssl-certificate-loader
    Creating /layers/paketo-buildpacks_azul-zulu/helper/exec.d/security-providers-configurer
    Creating /layers/paketo-buildpacks_azul-zulu/helper/exec.d/security-providers-classpath-9
  JVMKill Agent 1.16.0: Contributing to layer
    Downloading from https://github.com/cloudfoundry/jvmkill/releases/download/v1.16.0.RELEASE/jvmkill-1.16.0-RELEASE.so
    Verifying checksum
    Copying to /layers/paketo-buildpacks_azul-zulu/jvmkill
    Writing env.launch/JAVA_TOOL_OPTIONS.append
    Writing env.launch/JAVA_TOOL_OPTIONS.delim
  Java Security Properties: Contributing to layer
    Writing env.launch/JAVA_SECURITY_PROPERTIES.default
    Writing env.launch/JAVA_TOOL_OPTIONS.append
    Writing env.launch/JAVA_TOOL_OPTIONS.delim

...

Saving vehicle-api:azul...
*** Images (7928ea4ea26a):
      vehicle-api:azul
Adding cache layer 'paketo-buildpacks/azul-zulu:jdk'
Adding cache layer 'paketo-buildpacks/maven:application'
Adding cache layer 'paketo-buildpacks/maven:cache'
Successfully built image vehicle-api:azul

Run the following command.

docker run --rm -p 8080:8080 \
 -m 1g \
 -e MANAGEMENT_ENDPOINTS_WEB_EXPOSURE_INCLUDE="*" \
 -e SPRING_DATASOURCE_URL=jdbc:postgresql://host.docker.internal:5432/vehicle \
 vehicle-api:azul

The following log is output. You can see that Azul Zulu has been downloaded.

Setting Active Processor Count to 4
Calculated JVM Memory Configuration: -XX:MaxDirectMemorySize=10M -Xmx447509K -XX:MaxMetaspaceSize=89066K -XX:ReservedCodeCacheSize=240M -Xss1M (Total Memory: 1G, Thread Count: 250, Loaded Class Count: 13311, Headroom: 0%)
Adding 129 container CA certificates to JVM truststore
Spring Cloud Bindings Enabled
Picked up JAVA_TOOL_OPTIONS: -Djava.security.properties=/layers/paketo-buildpacks_azul-zulu/java-security-properties/java-security.properties -agentpath:/layers/paketo-buildpacks_azul-zulu/jvmkill/jvmkill-1.16.0-RELEASE.so=printHeapHistogram=1 -XX:ActiveProcessorCount=4 -XX:MaxDirectMemorySize=10M -Xmx447509K -XX:MaxMetaspaceSize=89066K -XX:ReservedCodeCacheSize=240M -Xss1M -Dorg.springframework.cloud.bindings.boot.enable=true
...

Run the following command. You can check the vendor name and version of Java that is actually running.

$ curl -s http://localhost:8080/actuator/env | jq ".propertySources[] | select(.name == \"systemProperties\").properties[\"java.vendor\"]"    
{
  "value": "Azul Systems, Inc."
}

$ curl -s http://localhost:8080/actuator/env | jq ".propertySources[] | select(.name == \"systemProperties\").properties[\"java.vm.version\"]"
{
  "value": "11.0.10+9-LTS"
}


TBD