Search code examples
dockercontainerspackbuildpackpaketo

Are Cloud Native Buildpacks just an automatic way to perform a multi-stage container image build?


I started to use pack to build container images for my applications. I used different builders for different apps: gcr.io/buildpacks/builder for a Node.js app and paketobuildpacks/builder:tiny for a Clojure app.

Not having to write a Dockerfile is great, but I'm still not sure how Cloud Native Buildpacks work. Are buildpacks just a bunch of executables (ran by a builder) that achieve the same result of a developer who manually writes a Docker multi-stage build (i.e. multiple FROM instructions in a Dockerfile)?


Solution

  • There are a few concepts to keep in mind

    1. A builder. A builder consists of a stack and one or more groups of buildpacks.
    2. A stack is a base build and run image.
    3. A buildpack is an OCI image with at least two binaries, detect and build.
    4. The lifecycle. The lifecycle responsible for running all of the builders.

    With that in mind, at a high level this is what happens

    When you run pack build. The cli takes all your information and uses that to perform the build. To do a build, it will set up a container. The container uses the build image from your stack, which includes the groups of buildpacks from the builder, and usually some development libraries exclusive to that image to make building your app easier. It then passes off the settings you input via the cli and runs the lifecycle inside that container.

    First, the lifecycle will run the detect script from each buildpack. The output of detect is a build plan, which the lifecycle uses to assemble the list of buildpacks that will participate in the build. This is how the pack cli and ultimately the lifecycle can build a Java app, a Node.js app or anything else supported by buildpacks. The build plan provides specific instructions on which buildpacks are required to do that.

    Second, the lifecycle will take the buildpacks that are participating in the build, again which is determined by detect and the selected buildplan, and run them in order. Each buildpack then runs and performs the actions that are required to build your application. Exactly what happens, depends on your buildpack, but a buildpack can do virtually anything. The net result of a buildpack running is a set of layers that are populated with information & files.

    The lifecycle then takes the run image from the builder and all the layers generated by the buildpacks that ran and marked as destined for the launch image and combines them into the output or launch image.

    The lifecycle also handles storing build and cached layers such that subsequent builds will be able to take advantage and run much faster.

    Dockerfiles or Buildpacks?

    The answer is that it's not one or the other. Each tool is good for certain things and you should use the best tool for the job.

    Buildpacks are targeting developers that have source code and wish to turn that into OCI images. Buildpacks eliminate the need to curate handcrafted, artisanal Dockerfiles for this task, freeing developers up to write more code and add value to their apps.

    The buildpacks accomplish this by wrapping up the patterns and best practices for creating OCI images for each programming language into an encapsulated, well-tested, and easy-to-use to use tool.

    Dockerfiles tend to be better suited for other tasks (i.e. not packaging up your application) such as making generic OS images or packing up servers like databases or message queues. In fact, Buildpacks use Dockerfiles to create the base build and run images that are part of the builder.