How to write a custom buildpack?

In this article you will learn how to write a custom buildpack. For simplicity, let's assume you want to write a buildpack which will install wkhtmltopdf binaries in the app that is being compiled.

First, we need to give the buildpack some meta information. In this meta information, we will provide information about what stacks this buildpack will run on, and what is the version and name of the buildpack. All this meta information about the buildpack is encompassed by a file called buildpack.toml.

For the custom buildpack we are writing, this is how the buildpack.toml will look like:

api = "0.7"

[buildpack]
  id = "neeto-deploy/wkhtmltopdf"
  version = "0.0.1"

[[stacks]]
  id = "heroku-22"
  id = "heroku-20

In the above example, we are setting the version as 0.0.1 and the name of the buildpack as neeto-deploy/wkhtmltopdf. And the stack that this buildpack will be compatible against as heroku-20 and heroku-22.

Currently, neetoDeploy only support heroku-22.

Now that you have setup the meta information related to a buildpack, you need to understand how a buildpack is actually compiled. There are three phases of buildpack installation.

  1. Detection phase

  2. Build phase

  3. Release phase

In all these three phases during which a buildpack is being compiled, custom code that you write will be run. These custom scripts are stored in the bin directory. And scripts which will be run individually during these three phases are present in bin/detect , bin/build, and bin/release files. So you can create these files under the bin directory.

  1. Detection Phase

This is the script that will be run to check if the buildpack needs to be compiled or not in the app. For example, you might want the buildpack to only work if it is a Ruby application, in which case, this script will check for the presence of certain files which will generally be present if it is a Ruby application. If this script exits with 0 code, then the buildpack will continue to run, if the script exits with 1 code, then the buildpack will not be compiled.

Asumming we want this buildpack to run in all kinds of application, let's write the code for the detection phase as follows:

#!/usr/bin/env bash

exit 0
  1. Build phase

This is the phase where we will be actually installing the binaries. But to install the binaries, we need to know which directory to install it under, so that the binary will work as expected.

#!/usr/bin/env bash
set -eo pipefail

echo "---> Wkhtmltopdf Buildpack"


BP_DIR=$(cd $(dirname ${0:-}); cd ..; pwd)

HOME_DIR=$(pwd)
BUILD_DIR=$(pwd)
TARGET_DIR=$HOME_DIR/.wkhtmltopdf
mkdir -p $TARGET_DIR

PROFILE_DIR=$HOME_DIR/.profile.d/

FONT_DIR=$TARGET_DIR/fonts

PKG_NAME=wkhtmltopdf.deb

cd $TARGET_DIR && curl -L https://github.com/wkhtmltopdf/packaging/releases/download/0.12.6.1-2/wkhtmltox_0.12.6.1-2.jammy_amd64.deb -o $PKG_NAME;
cd $TARGET_DIR && dpkg -x $PKG_NAME .
rm $TARGET_DIR/$PKG_NAME

mkdir -p $PROFILE_DIR
cp $BP_DIR/profile/wkhtmltopdf.sh $PROFILE_DIR
cat $BP_DIR/profile/wkhtmltopdf.sh >> $HOME_DIR/.bashrc

mkdir -p $FONT_DIR
cp $BP_DIR/fonts/* $FONT_DIR/
fc-cache -f $FONT_DIR

echo "export WKHTMLTOPDF_BIN=/app/.wkhtmltopdf/usr/local/bin/wkhtmltopdf" >> $HOME_DIR/.bashrc
  1. Release phase

Let's say you need the buildpack to make extra calls to an external services or perform additional tasks which are unrelated to the build phase. These kinds of scripts you may want to be run against the application which uses a buildpack are run in the release phase.

In our case, since we do not want to run any additional tasks, we will simply add the following script:

#!/usr/bin/env bash
echo "--- {}"

After following the above steps, you can push the changes to Github, and you can reference the Github URL in the buildpacks section, and the buildpack will be picked up during the next re-deployment.

If you need to modify the buildpack by introducing more changes to the build, detect or release phases, you can simply make the changes you need, and change the version in the buildpack.toml file, and neetoDeploy will pick up the latest changes of the custom buildpack automatically.

Can't find what you're looking for?