You can package applications as CNAB bundles using BuildKit, but the real magic is that BuildKit’s distributed nature means your bundle builds can happen anywhere, not just on your local machine or a dedicated CI runner.

Let’s see it in action. Imagine we have a simple web application and we want to package it using CNAB. We’ll define our application’s build and install steps in a Porter.yaml file, which is the standard for CNAB.

name: my-web-app
version: 0.1.0
description: A simple web app packaged as a CNAB bundle.
parameters:
  type: object
  properties:
    tag:
      type: string
      description: The Docker image tag for the web app.
      default: latest
    port:
      type: integer
      description: The port the web app should run on.
      default: 8080
      
build:
  # Build the Docker image for our web app

  - docker build -t {{.invocation.image}} .

  
install:
  # Run the web app container

  - docker run -d -p {{.parameters.port}}:80 {{.invocation.image}}


schema_version: 1.0.0

Now, we need a Dockerfile for our web app:

FROM nginx:alpine
COPY index.html /usr/share/nginx/html/
EXPOSE 80

And a simple index.html:

<!DOCTYPE html>
<html>
<head>
<title>Welcome!</title>
</head>
<body>
<h1>Hello from CNAB!</h1>
</body>
</html>

With BuildKit, we can build this CNAB bundle and push it to a registry. The buildx build command is our gateway. We’ll specify the output as a CNAB bundle (type=cnab) and tell it where to push the resulting image (--push).

buildx build \
  --platform linux/amd64 \
  --output type=cnab,name=myregistry.io/myuser/my-web-app:latest \
  --push \
  .

This command does a lot under the hood. BuildKit first interprets the Porter.yaml to understand the build and install steps. For the build step, it executes the docker build command specified there, creating a Docker image. Then, for the install step, it packages this Docker image along with the Porter.yaml and any other necessary files into a CNAB bundle. Finally, --push sends this bundle to myregistry.io/myuser/my-web-app:latest.

The core problem CNAB addresses is the "it works on my machine" syndrome for application deployments. Traditionally, you might have a Dockerfile for your app, a Kubernetes manifest, and Helm charts, but coordinating these across different environments and stages can be a nightmare. CNAB, or Cloud Native Application Bundle, provides a standardized specification for packaging and distributing cloud-native applications. It defines a manifest (like our Porter.yaml), an execution environment, and a set of operations (install, upgrade, uninstall).

BuildKit enhances this by allowing you to build these CNAB bundles using its distributed, parallel, and extensible build engine. Instead of a single, monolithic build process, BuildKit breaks down the build into smaller, independent operations that can be executed across multiple workers. This means your CNAB bundle builds can be significantly faster and more resilient. When you use buildx build --output type=cnab, BuildKit orchestrates the creation of the bundle, ensuring all its components are correctly assembled and ready for distribution.

Let’s dive deeper into the mental model. Think of a CNAB bundle as a self-contained deployment package. It includes everything an application needs to run: the application’s container image, the logic to install and manage it (defined in Porter.yaml), and any necessary dependencies or configurations. The build section in Porter.yaml is where you define how the application’s container image is created. The install, upgrade, and uninstall sections define the operational lifecycle. BuildKit, when configured with the cnab output type, acts as the builder that takes your application source, your Porter.yaml, and your Dockerfile, and produces a ready-to-distribute CNAB bundle image that can be stored in a container registry.

The surprising part is how BuildKit’s multi-platform build capabilities extend to CNAB. You can build a CNAB bundle that targets multiple architectures (e.g., linux/amd64, linux/arm64) simultaneously with a single buildx build command. BuildKit will produce a manifest list pointing to the appropriate bundle image for each platform, making your application universally deployable without manual intervention for cross-architecture builds.

After successfully building and pushing your CNAB bundle, the next step in your journey will likely be to actually run that bundle. You’ll use a CNAB runtime like Porter to invoke the install operation defined in your Porter.yaml, pulling the bundle from the registry and executing the specified Docker commands.

Want structured learning?

Take the full Buildkit course →