This guide teaches how to create a custom builder by assembling files and configuring them step by step.
In this guide, you’ll learn how to create your own builder by assembling the necessary files and configuring them step by step. We will begin by creating a folder named “builder” to hold all the essential files for your custom builder.─────────────────────────────
The first step is to establish the base image used during the build stage. This image is crucial as it defines the environment for processing buildpacks and ultimately creating the final runtime image.
Create a Dockerfile named build-base.Dockerfile with the following content. This file uses Ubuntu Jammy as the base image, installs required utilities (including xz-utils, ca-certificates, jq, wget, and curl), and adds the YJ tool. It also configures the CNB user and group information.
Copy
Ask AI
# Define the base imageFROM ubuntu:jammy# Install packages that we want to make available at build timeRUN apt-get update && \ apt-get install -y xz-utils ca-certificates jq wget curl && \ rm -rf /var/lib/apt/lists/*RUN curl -Lo yj https://github.com/sclevine/yj/releases/download/v5.1.0/yj-linux-amd64 && chmod +x yj && mv yj /usr/local/bin/# Set required CNB user informationARG cnb_uid=1000ARG cnb_gid=1000ENV CNB_USER_ID=${cnb_uid}ENV CNB_GROUP_ID=${cnb_gid}
When you build this image, you may observe logs similar to the example below:
Copy
Ask AI
[exporter] Adding layer 'buildpacksio/lifecycle:process-types'[exporter] Adding label 'io.buildpacks.lifecycle.metadata'[exporter] Adding label 'io.buildpacks.project.metadata'[exporter] Setting default process type 'web'[exporter] Saving test...*** Images (a303c1438de1):test[exporter] Adding cache layer 'my-js-buildpack:node-dependencies'[exporter] Adding cache layer 'my-js-buildpack:node-js'[exporter] Exporting built image test
To further enhance your base image, add commands to create a dedicated user and group for CNB and label your distribution.
Create a file named run-base.dockerfile with the following content. The runtime Dockerfile is similar to the build image but installs fewer packages.
Copy
Ask AI
# Define the base imageFROM ubuntu:jammy# Install packages that we want to make available at run-timeRUN apt-get update && \ apt-get install -y xz-utils ca-certificates && \ rm -rf /var/lib/apt/lists/*# Create user and groupARG cnb_uid=1000ARG cnb_gid=1000RUN groupadd cnb --gid ${cnb_gid} && \ useradd --uid ${cnb_uid} --gid ${cnb_gid} -m -s /bin/bash cnb# Set user and groupUSER ${cnb_uid}:${cnb_gid}# Set required CNB target informationLABEL io.buildpacks.base.distro.name="ubuntu"LABEL io.buildpacks.base.distro.version="22.04"
A sample build log for the runtime image may look like this:
Copy
Ask AI
[internal] load metadata for docker.io/library/ubuntu:jammy[internal] load .dockerignore=> transferring context: 2B[1/4] FROM docker.io/library/ubuntu:jammy@sha256:0e5a475c249294aafc340fcd541e9a456aab7296681a3994d613587203f97CACHED [2/4] RUN apt-get update && ...CACHED [3/4] RUN curl -Lo yj https://github.com/sclevine/yj/releases/download/v5.1.0/yj-linux-amd64 && chmod +x yj &&CACHED [4/4] RUN groupadd cnb --gid 1000 && ...exporting to imagewriting image sha256:04d2c26afe940c736053a38dc4dcfa12c299e68624a42e6c2e4de309
With both the build and runtime images in place, the next step is to configure your builder using a builder.toml file. This configuration file specifies the buildpacks to include and outlines the order in which they will execute during the detection phase.
Create the builder.toml file in your builder directory with the following contents. In this example, we include a JavaScript buildpack and a sample buildpack:
Copy
Ask AI
# Buildpacks to include in builder[[buildpacks]]uri = "docker://sanjeevkt720/my-js-buildpack"[[buildpacks]]uri = "docker://cnbs/sample-package:hello-universe"
Define the order in which your buildpacks will run. In this case, the JavaScript buildpack runs first followed by the sample buildpack:
Copy
Ask AI
[[order]] [[order.group]] id = "my-js-buildpack" [[order.group]] id = "samples/hello-universe"
Once the builder image is successfully created (verify using docker image ls), you can test it by building a sample image from your Node.js application. Change to the directory containing your application and execute:
During the build process, you will see several distinct phases:
ANALYZING – Checks for an existing image.
DETECTING – Runs the detection phase for your buildpacks (e.g., my-js-buildpack, samples/hello-world, and samples/hello-moon).
RESTORING and BUILDING – Installs Node.js (for example, version 18.18.1) along with its dependencies, caches layers, and finalizes the image.
An excerpt of the build logs could include:
Copy
Ask AI
0.20.0: Pulling from buildpacksio/lifecycleDigest: sha256:bald717ec095f94eb75a667a2fe4178f6f5de6430c89c7168cd04fcfd3Status: Image is up to date for buildpacksio/lifecycle:0.20.0=== ANALYZING[analyzer] Image with name "my-image" not found===> DETECTING[detector] my-js-buildpack 0.0.1[detector] samples/hello-world 0.0.1[detector] samples/hello-moon 0.0.1===> RESTORING===> BUILDINGBuilding image using my-js-buildpack buildpack[builder] nodejs version: 18.18.1[builder] cached version: null
After the build completes, you should see confirmation messages similar to the following:
Ensure that the builder image is correctly created before attempting to build your application, as this step validates your configuration and buildpack order.
In this lesson, you have learned how to create a custom builder for buildpacks by:
Defining a base image (build-base) and a runtime image (run-base) using dedicated Dockerfiles.
Configuring a builder.toml file to list the required buildpacks and specify the detection order.
Creating and testing the builder using the Pack CLI to build a sample Node.js application image.
This systematic approach guarantees that your buildpacks execute in the correct sequence and that your final runtime image contains only the necessary dependencies for optimal performance.For more details, refer to Kubernetes Documentation or check out the Pack CLI documentation.