Buildpack API
Last updated August 02, 2024
Table of Contents
Buildpacks are the scripts that power app builds on Heroku. Check out the Buildpacks article for an overview of what role buildpacks play on the Heroku platform.
Buildpack API
We encourage buildpack developers to use sh
or bash
to ensure compatibility with future Heroku stacks.
A buildpack consists of three scripts:
bin/detect
: Determines whether to apply this buildpack to an app.bin/compile
: Used to perform the transformation steps on the app.bin/release
: Provides metadata back to the runtime.
bin/detect
Usage
bin/detect BUILD_DIR
The name sent to stdout
will be displayed as the application type during push.
Summary
This script takes BUILD_DIR
as a single argument and must return an exit code of 0
if the app present at BUILD_DIR
can be serviced by this buildpack. If the exit code is 0
, the script must print a human-readable framework name to stdout
.
Example
#!/bin/sh
# this pack is valid for apps with a hello.txt in the root
if [ -f $1/hello.txt ]; then
echo "HelloFramework"
exit 0
else
exit 1
fi
bin/compile
Usage
bin/compile BUILD_DIR CACHE_DIR ENV_DIR
Summary
The contents of CACHE_DIR
will be persisted between builds. You can cache the results of long processes like dependency resolution here to speed up future builds.
This script performs the buildpack transformation. BUILD_DIR
will be the location of the app and CACHE_DIR
will be a location the buildpack can use to cache build artifacts between builds.
All output received on stdout
from this script will be displayed to the user (for interactive git-push builds) and stored with the build.
The application in BUILD_DIR
along with all changes made by the compile
script will be packaged into a slug.
ENV_DIR
is a directory that contains a file for each of the application’s configuration variables. Config vars are made available as environment variables during execution of commands specified in the Procfile, as well as when running one-off processes.
The application config vars are passed to the buildpack as an argument (versus set in the environment) so that the buildpack can optionally export none, all or parts of the app config vars available in ENV_DIR
when the compile
script is run. Exporting the config can be scoped to only certain steps in the compile script or to the entire compile phase.
The reason application config vars are passed to the compile script in individual files is to make it easier for buildpacks to correctly handle config vars with multi-line values
The name of the file is the config key and the contents of the file is the config value. The equivalent of config var S3_KEY=8N029N81
is a file with the name S3_KEY
and contents 8N029N81
.
Below is a Bash function demonstrating how to export the content of the ENV_DIR
into the environment.
export_env_dir() {
env_dir=$1
acceptlist_regex=${2:-''}
denylist_regex=${3:-'^(PATH|GIT_DIR|CPATH|CPPATH|LD_PRELOAD|LIBRARY_PATH)$'}
if [ -d "$env_dir" ]; then
for e in $(ls $env_dir); do
echo "$e" | grep -E "$acceptlist_regex" | grep -qvE "$denylist_regex" &&
export "$e=$(cat $env_dir/$e)"
:
done
fi
}
Use the regular expressions to blocklist or allowlist config keys for export. For example, config vars like PATH can sometimes conflict with build tools so blocklisting that might be a good idea. allowlisting is recommended if you know that builds will only require a specific subset of config vars (like DATABASE_URL
) to run.
The following environment variables are available during the compile phase:
STACK
: see the Stacks section below for details on how to use thisSOURCE_VERSION
: the “version” of the source code currently being built. For git-push builds, this is the git commit SHA-1 hash.
Style
Buildpack developers are encouraged to match the Heroku push style when displaying output.
-----> Main actions are prefixed with a 6-character arrow
Additional information is indented to align
Whenever possible display the versions of the software being used.
-----> Installing dependencies with npm 1.0.27
Side effects
Buildpack developers should avoid generating side effects during the build. For example, it is not recommended to perform database migrations in the compile script.
Example
#!/bin/sh
indent() {
sed -u 's/^/ /'
}
echo "-----> Found a hello.txt"
# if hello.txt has contents, display them (indented to align)
# otherwise error
if [ ! -s $1/hello.txt ]; then
echo "hello.txt was empty"
exit 1
else
echo "hello.txt is not empty, here are the contents" | indent
cat $1/hello.txt
fi | indent
bin/release
Usage
bin/release BUILD_DIR
Summary
addons
will only be applied the first time an app is deployed.
BUILD_DIR
will be the location of the app.
This script returns a YAML formatted hash with two keys:
addons
is a list of default addons to install.default_process_types
is a hash of defaultProcfile
entries.
This script will only be run if present.
Note: The buildpack’s export
script is not sourced prior to bin/release
being run, so any environment
variables set in export
will not be available (such as those adding runtime binaries to PATH
). If they
are required, the export
script will need to be sourced manually.
Example
#!/bin/sh
cat << EOF
---
addons:
- heroku-postgresql
default_process_types:
web: bin/node server.js
EOF
Instead of using default_process_types
, you may also write a default Procfile
if one isn’t provided.
.profile.d
scripts
During startup, the container starts a bash
shell that source’s all .sh
scripts in the .profile.d/
directory before executing the dyno’s command. An application’s config vars will already be present in the environment at the time the scripts are sourced.
This allows buildpacks to manipulate the initial environment for all dyno types in the app. Potential use cases include defining initial config values like $PATH
by exporting them into the environment, or performing other initialization steps necessary during dyno startup.
Like the standard Linux /etc/profile.d
shell startup files, these must be bash
scripts, and their filenames must end in .sh
. No guarantee is made regarding the order in which the scripts are sourced.
Scripts in .profile.d/
should only be written by buildpacks. If you need to perform application-specific initialization tasks at the time a dyno boots, you should use .profile scripts, which are guaranteed to run after the scripts in .profile.d/
.
Example .profile.d/mybuildpack-defaults.sh
It is recommended to prefix script filenames using a common identifier, e.g. based on the name of the buildpack. Otherwise, collisions are possible if an application uses multiple buildpacks that use the same script filenames.
# add vendor binaries to the path
export PATH=$PATH:$HOME/vendor/bin
# set a default LANG if it does not exist in the environment
export LANG=${LANG:-en_US.UTF-8}
To avoid overwriting an existing config var value, use a technique like the ${var:-default}
parameter substitution shown above for the $LANG
variable.
Composing multiple buildpacks
An app can be composed by multiple buildpacks
which run in sequence.
In order to provide an environment for subsequent buildpacks,
a buildpack can create a $buildpack_dir/export
script to
be executed before the following buildpack is run.
For example, the Node.js buildpack uses this to provide a node binary for subsequent buildpacks to use.
Caching
Use caution when storing large amounts of data in the CACHE_DIR
as the full contents of this directory is stored in S3 and must be network-transferred back and forth each time this app is built. A large CACHE_DIR
can introduce a significant delay to the build process.
The bin/compile
script will be given a CACHE_DIR
as its second argument which can be used to store artifacts between builds. Artifacts stored in this directory will be available in the CACHE_DIR
during successive builds. This build cache is available only during slug compilation, and is specific to the app being built.
If the buildpack does intend to use a cache, it should create the CACHE_DIR directory if it doesn’t exist.
Buildpacks often use this cache to store resolved dependencies to reduce build time on successive deploys.
Heroku users can use the heroku-builds plugin to clear the build cache created by the buildpack they use for their app.
Stacks
Heroku supports multiple OS base images, called stacks.
If you’re maintaining a buildpack, it’s your responsibility to make sure that the buildpack can create slugs that works on all the stacks that Heroku supports. When Heroku runs buildpacks to create slugs for an app, the build is always run in a dyno using the same stack as the app that the build is running for. In addition, a STACK
environment variable is available during the compile phase so that a buildpack can determine the destination stack for the build. Example STACK
values are heroku-20
, heroku-22
and heroku-24
.
If your buildpack does not support a particular stack, the compile script can optionally print an error and exit with a non-zero exit code. Heroku recommends supporting all stacks, however.
Buildpacks should use the STACK
variable to selectively pull binaries and other dependencies appropriate for that stack. Typically, binaries that are vendored in by buildpacks have to be built in different versions for each stack to ensure compatibility. See the Binaries section below for details.
Binaries
A buildpack is responsible for building a complete executable package around the app. This may necessitate including language VMs and other system dependencies that are needed by the app to run.
When building and packaging dependencies, make sure they are compatible with the stacks currently in use on Heroku. It may be necessary to compile and distribute dependencies separately for each stack and vendor in the appropriate set for the app currently being built. One way to do that is to build dependencies in Heroku dynos, for example by using heroku run
.
Publishing and Sharing Buildpacks
If you would like to share your buildpack with the Heroku community, you can register your buildpack. After registering, your buildpack will be displayed in the heroku buildpacks:search
CLI command and Heroku Elements (late 2018).
Example Buildpacks
The easiest way to get started creating a buildpack is to examine or fork one of the many existing buildpacks: