Kpack and Paketo’s different stack types

Exploring building images with full, base, and tiny stacks

Amit Singh
7 min readJul 2, 2023
Kpack + Paketo

Alright, so before talking about building app images, let’s very briefly go over what Kpack is.
Kpack lets you leverage Kubernetes to build OCI images, with a platform implementation of Cloud Native Buildpacks as the underlying mechanism. Basically, your application images will be built on a Kuberentes cluster and pushed directly to a registry.

The following tutorial does have some prerequisites, which I’m going to list below:

Once you’re all caught up, we can start building images with Kpack.

Setting things up

Kpack needs a Kubernetes cluster to run on, so there are some steps we need to perform before we dive deeper.

  • Install kubectl following instructions here.
  • If you can access a Kubernetes cluster on cloud (EKS, GKE, etc.), authenticate to it otherwise you can install minikube and create one locally as follows:
 minikube start --memory 6144 --cpus 3 --kubernetes-version=v1.24.13

Note: you can adjust the memory and cpu to allocate to the minikube cluster. I ran this tutorial with kubernetes v1.24.13, but you can try testing with newer versions too.

  • Kpack would push Builder and app images to a registry. You can create repositories under your Dockerhub account to store these images.
    For this tutorial, you’d need two repos named: kpack-image and kpack-builder.

Note: Make sure to replace the username semmet95 with your dockerhub account username throughout the repo.

Once you have created the repos and have access to a kubernetes cluster, create a docker-registry secret to authenticate kpack resources to push images to your repo.

kubectl create secret docker-registry dockerhub-creds --docker-username=<dockerhub-username> --docker-password=<dockerhub-password>

Getting started with the repo

The repo contains kpack resources that are deployed by the script kpack-setup.sh and the end result is an OCI image of an app. For this tutorial, we will be creating an OCI image for the spring-petclinic project, a simple tomcat app example. Although Kpack does let you build images directly from the source repo, we will be using the version of the project that is used by the sample Image resource in kpack’s repo.

https://storage.googleapis.com/build-service/sample-apps/spring-petclinic-2.1.0.BUILD-SNAPSHOT.jar

Let’s go over each of the files in the repo

clusterstack.yaml

Definition for a ClusterStack resource that contributes OS layer to the app image. The properties of the resource are set based on the kind of stack being used, i.e., full, base, or tiny.

clusterstore.yaml

Definition for a ClusterStore resource that acts like a storage unit for buildpack references that are later added as layers to the app image by a Builder. Since our app, spring-petclinic, is a java based project, Paketo’s java buildpack, paketobuildpacks/java, alone would be enough to provide all the run and build time dependencies for the app.

clusterbuilder.yaml

ClusterBuilder resource brings ClusterStore and ClusterStack together and is the resource that’s directly used to build the app OCI image. A ClusterBuilder is comprised of all the essential building blocks for an app image, which it packs as an image and pushes to a registry which is specified under the tag field. This is where the dockerhub repo we talked about earlier, kpack-builder, comes into the picture. In order to authenticate the ClusterBuilder to push images to your repo you’d need a serviceaccount mounting the secret dockerhub-creds created in the Setting things up section. This can be done by referring to it in the serviceAccountRef field of the ClusterBuilder.

serviceaccount.yaml

This is the serviceaccount that mounts the dockerhub-creds secret and can be used to authenticate kpack resources, ClusterBuilder and Image in this case, to push images to your dockerhub repos.

image.yaml

Kpack Image resource corresponds to an OCI image with a particular tag (the tag field of the resource is immutable). You can have multiple builds under the same Image but all of them will push the built image with the same tag/URI. Image resource gives you the flexibility to configure the build time environment with its build field. In our case we are using the BP_JAVA_VERSION environment variable to tell what java version to download and contribute the app image during the build, that being Java 8. We also have to specify:

  • the ClusterBuilder we want to use here (providing all the run/build time dependencies)
  • cache size (optional) to store downloaded dependencies that could be reused during subsequent builds (stored in a pvc)
  • serviceaccount to authenticate the Image resource to push app images to your dockerhub repo, kpack-image
  • url of the source blob, a jar file in this case, that was mentioned at the beginning of this section. This is the app we will be creating OCI image for.
  • tag to push the app image to. In this case, to your dockerhub account’s kpack-image repo

Apart from these kpack resources we also have a regular pod definition, app.yaml in the examples directory, exposing port 8080, so you can access the container local port via port-forward. We will use this pod to test the images we build.

kpack-setup.sh

Now we come to the most important file in the repo. This script performs the following operations:

  • installs kpack controller on the cluster
  • deploys the ClusterStore and ServiceAccount resources
  • updates the ClusterStack definition based on the stack related environment variables and deploys it
  • updates the ClusterBuilder resource name and the name of the ClusterStack to use
  • updates the Image resource name to refer to the correct ClusterBuilder, updates the tag field and name of the resource based on the kind of stack being used.

Executing the script

Time to get to the action part. The script reads environment variables to determine which kpack version to install on the cluster and which stack to use to build the app image.

First, set the version of kpack to install.

export KPACK_VERSION=0.10.1

Now, set the ID of the stack you want to use to build the app image. To start with, we will be using the Bionic Full stack.

export STACK_ID=io.buildpacks.stacks.bionic

We also need to set the run image, or the OS layer, to run the app on.

export RUN_IMAGE=paketobuildpacks/run:full-cnb

Now run the script

chmod +x ./kpack-setup.sh
./kpack-setup.sh
A quick overview of everything the script does

For those of you who don’t know, I’m using k9s tool here to quickly check on the kubernetes resources managed by the script.

If the app image is built successfully, you can now check your dockerhub repos to verify if they have been pushed.

ClusterBuilder repo

The app image would be pushed with full-cnb tag by the script because we are using the full-cnb tag here.

App image pushed with full-cnb tag to the kpack-image repo

Building the app image with base and tiny stacks

To use any stack, just change the STACK_ID and RUN_IMAGE accordingly and run the script.
To use the Bionic Base stack,

export STACK_ID=io.buildpacks.stacks.bionic
export RUN_IMAGE=paketobuildpacks/run:base-cnb
./kpack-setup.sh

Once the script is done, you’ll have the app image pushed to the kpack-image repo

App image pushed with base-cnb tag to the kpack-image repo

And, to use the Bionic Tiny stack,

export STACK_ID=io.paketo.stacks.tiny
export RUN_IMAGE=paketobuildpacks/run:tiny-cnb
./kpack-setup.sh
App image pushed with tiny-cnb tag to the kpack-image repo

Now that our app images are ready, let's run them in a pod and test if the app is accessible. For this, we will be using the image built with tiny-cnb stack.
Update the app.yaml file to pull the corresponding image from your dockerhub account’s kpack-image repo.

apiVersion: v1
kind: Pod
metadata:
name: petclinic
spec:
containers:
- image: <your-dockerhub-username>/kpack-image:tiny-cnb
name: petclinic
ports:
- containerPort: 8080

Run the following commands to deploy the pod and enable port-forward to access port 8080 of the app container.

kubectl apply -f ./examples/app.yaml 
kubectl port-forward petclinic 8999:8080

And here is the app.

Accessing the app pod via port-forward

Stack Comparison

Alright, now that we have working images built on different stack layers, we can compare their sizes.

Stack size comparison

As you can see, the image size reduces to less than 1/3rd when we use the tiny-cnb stack. That’s because while full-cnb contributes an OS layer that’s described by Paketo as:

ubuntu:bionic with many common dependencies like rsync and openss

and base-cnb stack contributes a layer that is:

ubuntu:bionic with some common dependencies like tzdata and openssl

tiny-cnb is distroless like with no shell and very limited number of packages. It adds an OS layer ideal for most Golang apps and Java GraalVM Native Images.
Paketo recommends using the smallest stack that supports your app. Since our app is a simple spring project, we can deploy it using the tiny stack and significantly reduce the image size.

And there you go, you can follow this guide to create images for different apps using different stacks. Please share your results in the comments section below, I’m curious to see if you guys notice any difference depending on the stack used.
Also, if you have any questions or suggestions, drop them in the comments section as well.
See you on another post 🖖

Hey 👋 there. If I helped you in some way, and you’re feeling generous today, you can now buy me a coffee!

--

--

Amit Singh

A Software Engineer who believes that technological progress should be more about extension than replacement.