If you haven’t already, check out our tutorial on extensions, then come here for the finer points.
The build plan is a mechanism for inter-buildpack communication. Through the build plan, buildpacks may express the dependencies they require, as well as those they provide. The lifecycle uses information from the build plan to determine whether a group of buildpacks is compatible - that is, whether for every buildpack in the group, its required dependencies are provided by a buildpack that comes before it.
Extensions can use the build plan too - but they are only allowed to provide dependencies, they cannot require any.
Note that because there is a separate order for extensions that is prepended to each buildpack group during the detect
phase,
all extension “provides” come before all buildpack “requires” in the build plan.
During the detect
phase, the lifecycle sets a CNB_OUTPUT_DIR
environment variable when executing each ./bin/detect
.
If using a build plan, extensions should write the plan to $CNB_OUTPUT_DIR/plan.toml
.
During the generate
phase, extensions may output (in addition to Dockerfiles) an extend-config.toml
containing build-time arguments for Dockerfiles.
(Not to be confused with the build
phase, “build” here refers to the application of Dockerfiles to a base image,
similar to a docker build
).
Arguments may be configured for builder image extension or runtime base image extension or both, according to the schema defined in the spec.
During the generate
phase, the lifecycle sets a CNB_OUTPUT_DIR
environment variable when executing each ./bin/generate
.
If using an extend-config.toml
, extensions should write the config to $CNB_OUTPUT_DIR/extend-config.toml
.
Whenever possible, the application of Dockerfiles to a base image will use a caching mechanism
similar to that of a docker build
.
The lifecycle, for example, uses kaniko
to implement the extender
.
However, there may be times when caching is not desired - for example, when fetching the “latest” available version of a package.
In such cases, Dockerfiles can use the build_id
build argument to invalidate the cache for all instructions that follow.
As an example:
RUN echo "this instruction may be found in the cache"
ARG build_id=0
RUN echo ${build_id}
RUN echo "this instruction should never be found in the cache, as the value above will change"
Note that build_id
is defaulted to 0
as a best practice.
Dockerfiles from image extensions may contain USER root
instructions in order to perform actions that would not be possible
when running as a non-root user.
However, for security reasons, the final user after all Dockerfiles have been applied should not be root.
To reset the user to its original value (before the application of the current Dockerfile),
Dockerfiles should make use of user_id
and group_id
build arguments, as seen below:
ARG user_id=1000
ARG group_id=1000
USER ${user_id}:${group_id}
Image layers generated from extensions are “above the rebasable seam” - that is,
after swapping the runtime image to an updated version through a rebase
operation,
the extension layers will be persisted in the rebased application image.
Unlike buildpack layers, extension layers are not always safe to rebase. Extension layers may be safe to rebase if:
By default, the lifecycle will assume that any extension layers are not rebasable.
To indicate otherwise, run.Dockerfile
s should include:
LABEL io.buildpacks.rebasable=true
If all run.Dockerfile
s set this label to true
, the application image will contain the label io.buildpacks.rebasable=true
.
Otherwise, the application image will contain the label io.buildpacks.rebasable=false
.
pack rebase
requires a --force
flag if the application image contains io.buildpacks.rebasable=false
.
Extension authors should take great care (and perform testing) to ensure that any layers marked as rebasable are in fact rebasable.
Extension IDs must be globally unique to extensions, but extensions and buildpacks can share the same ID.
Just like buildpack.toml
, an extension.toml
can contain additional metadata to describe its behavior.
See the spec for more information.
The root directory for a typical extension might look like the following:
.
├── bin
│ ├── detect
│ ├── generate
├── extension.toml
But it could also look like any of the following:
If ./bin/detect
is missing,
the extension is assumed to pass detection and
the lifecycle will interpret the contents of ./detect
as the contents of $CNB_OUTPUT_DIR
.
.
├── bin
│ ├── generate
├── detect
│ ├── plan.toml
├── extension.toml
If ./bin/generate
is missing,
the lifecycle will interpret the contents of ./generate
as the contents of $CNB_OUTPUT_DIR
.
├── bin
│ ├── detect
├── generate
│ ├── build.Dockerfile
│ ├── run.Dockerfile
├── extension.toml
├── detect
│ ├── plan.toml
├── generate
│ ├── build.Dockerfile
│ ├── run.Dockerfile
├── extension.toml