Skip to content

Migrate to Cloud Native Buildpacks

Time to Complete: 25 minutes Goal: Replace your Dockerfile with Cloud Native Buildpacks (CNB) for automated, secure, and maintainable container builds.

What You'll Learn

By the end of this tutorial, you will have:

  1. ✅ Understood the benefits of Cloud Native Buildpacks over Dockerfiles
  2. ✅ Removed your Dockerfile and configured Buildpacks
  3. ✅ Built a container image using Buildpacks
  4. ✅ Deployed the Buildpack-built image to Fawkes
  5. ✅ Verified automatic base image updates (rebasing)

Prerequisites

Before you begin, ensure you have:

Why Buildpacks?

Dockerfiles give you control, but with control comes responsibility. Who updates base images when CVEs are discovered? Who ensures all teams follow security best practices? Buildpacks automate these concerns. Learn the philosophy.

Step 1: Understand Your Current Dockerfile

Let's review what your Dockerfile is doing and how Buildpacks will replace it.

  1. View your current Dockerfile:
FROM node:18-alpine

WORKDIR /app

COPY package*.json ./
RUN npm ci --only=production

COPY server.js ./
COPY tracing.js ./
COPY vault-client.js ./

RUN addgroup -g 1000 appuser && \
    adduser -D -u 1000 -G appuser appuser && \
    chown -R appuser:appuser /app

USER appuser

EXPOSE 8080

CMD ["npm", "start"]
  1. Identify what the Dockerfile is doing:

  2. ✅ Choosing a base image (node:18-alpine)

  3. ✅ Installing dependencies (npm ci)
  4. ✅ Copying application code
  5. ✅ Creating a non-root user
  6. ✅ Setting the start command

  7. Problems with this approach:

  8. Manual updates: When Node.js 18 reaches EOL, you must update it
  9. CVE lag: Security patches require rebuilding every image
  10. Inconsistency: Different teams use different base images
  11. No caching: Every team implements their own layer caching

Buildpacks Automate All of This

Buildpacks detect your application type, choose appropriate base images, install dependencies, and configure security - all automatically.

Checkpoint

You understand what your Dockerfile does and why Buildpacks are an improvement.

Step 2: Install Pack CLI

Pack is the CLI for working with Cloud Native Buildpacks.

  1. Install Pack (choose your platform):

macOS (Homebrew):

brew install buildpacks/tap/pack

Linux:

sudo add-apt-repository ppa:cncf-buildpacks/pack-cli
sudo apt-get update
sudo apt-get install pack-cli

Windows (Chocolatey):

choco install pack
  1. Verify installation:
pack --version
  1. Set a default builder (we'll use Paketo):
    pack config default-builder paketobuildpacks/builder:base
    

What's a Builder?

A builder is a container image that contains buildpacks and knows how to build your application. Paketo Buildpacks is a CNCF project that provides enterprise-grade buildpacks for multiple languages.

Checkpoint

Pack CLI is installed and configured with a default builder.

Step 3: Prepare for Buildpacks

Buildpacks work best when your application follows conventions. Let's ensure your app is ready.

  1. Verify your package.json has a start script:
{
  "name": "hello-fawkes",
  "version": "3.0.0",
  "scripts": {
    "start": "node server.js"
  },
  "dependencies": {
    "express": "^4.18.2",
    "node-vault": "^0.10.2",
    "@opentelemetry/api": "^1.4.1",
    "@opentelemetry/sdk-node": "^0.41.0",
    "@opentelemetry/auto-instrumentations-node": "^0.39.1",
    "@opentelemetry/exporter-trace-otlp-http": "^0.41.0"
  }
}
  1. Ensure you have a package-lock.json:
npm install
  1. Optional: Create a .packignore file to exclude files from the build:
.git/
.gitignore
*.md
Dockerfile
k8s/
node_modules/
  1. Commit these changes:
    git add .packignore package.json package-lock.json
    git commit -m "Prepare for Buildpacks migration"
    

Checkpoint

Your application follows Buildpacks conventions and is ready to build.

Step 4: Build with Buildpacks

Now for the exciting part - building your first Buildpack image!

  1. Build your application with Pack:
pack build YOUR-USERNAME/hello-fawkes:v4.0.0-buildpack \
  --builder paketobuildpacks/builder:base \
  --env BP_NODE_VERSION=18
  1. Watch the build process:

  2. Buildpacks will detect that you're using Node.js

  3. It will install the correct Node.js version
  4. It will run npm ci to install dependencies
  5. It will configure the start command
  6. It will create a secure, minimal container image

  7. Inspect the build output:

===> DETECTING
[detector] 6 of 24 buildpacks participating
[detector] paketo-buildpacks/ca-certificates   3.6.3
[detector] paketo-buildpacks/node-engine        2.0.0
[detector] paketo-buildpacks/npm-install        1.2.3
[detector] paketo-buildpacks/node-run-script    1.0.5
[detector] paketo-buildpacks/node-start         1.0.5
[detector] paketo-buildpacks/procfile           5.6.3

===> BUILDING
[builder] Running npm ci...
[builder] Installing node_modules...

===> EXPORTING
[exporter] Adding layer 'paketo-buildpacks/node-engine:node'
[exporter] Adding layer 'paketo-buildpacks/npm-install:modules'
  1. Verify the image was created:
    docker images | grep hello-fawkes
    

Build Time

The first build may take a few minutes as it downloads base images and buildpacks. Subsequent builds are much faster thanks to layer caching.

Checkpoint

You've built a container image using Cloud Native Buildpacks!

Step 5: Compare Image Sizes

Let's see how the Buildpack image compares to your Dockerfile image.

  1. Check Dockerfile image size:
docker images YOUR-USERNAME/hello-fawkes:v3.0.0
  1. Check Buildpack image size:
docker images YOUR-USERNAME/hello-fawkes:v4.0.0-buildpack
  1. Compare the layers:
# Dockerfile image
docker history YOUR-USERNAME/hello-fawkes:v3.0.0

# Buildpack image
docker history YOUR-USERNAME/hello-fawkes:v4.0.0-buildpack

Image Size Differences

Buildpack images may be larger than Alpine-based Dockerfile images because they use Ubuntu Bionic for better compatibility. However, they're more secure and maintainable. The trade-off is worth it for most applications.

Checkpoint

You understand the characteristics of your Buildpack-built image.

Step 6: Test the Buildpack Image Locally

Before deploying, let's verify the image works correctly.

  1. Run the container locally:
docker run -d --name hello-fawkes-test \
  -p 8080:8080 \
  -e PORT=8080 \
  -e VAULT_ADDR=http://vault.fawkes-platform.svc.cluster.local:8200 \
  YOUR-USERNAME/hello-fawkes:v4.0.0-buildpack
  1. Test the endpoint:
curl http://localhost:8080/

Should return:

{
  "message": "Hello from Fawkes!",
  "timestamp": "2025-12-06T12:00:00.000Z",
  "version": "3.0.0",
  "tracing": "enabled",
  "secrets": "managed by Vault"
}
  1. Check the running user (should be non-root):
docker exec hello-fawkes-test whoami

Should output: cnb (Buildpacks default non-root user)

  1. Stop and remove the test container:
    docker stop hello-fawkes-test
    docker rm hello-fawkes-test
    

Checkpoint

The Buildpack image works correctly and follows security best practices!

Step 7: Deploy Buildpack Image to Fawkes

Now let's deploy the Buildpack-built image to your cluster.

  1. Push the image to your registry:
docker push YOUR-USERNAME/hello-fawkes:v4.0.0-buildpack
  1. Update k8s/deployment.yaml to use the new image:
apiVersion: apps/v1
kind: Deployment
metadata:
  name: hello-fawkes
  namespace: my-first-app
  labels:
    app: hello-fawkes
    version: v4
spec:
  replicas: 2
  selector:
    matchLabels:
      app: hello-fawkes
  template:
    metadata:
      labels:
        app: hello-fawkes
        version: v4
      annotations:
        buildpack.io/builder: "paketobuildpacks/builder:base"
    spec:
      serviceAccountName: hello-fawkes
      containers:
        - name: hello-fawkes
          image: YOUR-USERNAME/hello-fawkes:v4.0.0-buildpack
          # Rest of spec remains the same
  1. Commit and push:
git add k8s/deployment.yaml
git commit -m "Deploy Buildpack-built image"
git push
  1. Watch the rollout:
kubectl rollout status deployment/hello-fawkes -n my-first-app
  1. Verify the new pods are running:
kubectl get pods -n my-first-app
  1. Test the deployed service:
    curl https://hello-fawkes.127.0.0.1.nip.io/
    

Checkpoint

Your service is now running with a Buildpack-built image on Fawkes!

Step 8: Understand Rebasing

One of the key benefits of Buildpacks is "rebasing" - updating base images without rebuilding.

  1. Check the current base image:
pack inspect YOUR-USERNAME/hello-fawkes:v4.0.0-buildpack

Note the "Run Image" section.

  1. Rebase to the latest base image (simulating a security update):
pack rebase YOUR-USERNAME/hello-fawkes:v4.0.0-buildpack
  1. Compare the speed:

  2. Rebuild: 2-5 minutes (downloads dependencies, runs build)

  3. Rebase: 5-10 seconds (only updates base layers)

  4. Push the rebased image:

docker push YOUR-USERNAME/hello-fawkes:v4.0.0-buildpack
  1. Trigger a rollout in Kubernetes:
    kubectl rollout restart deployment/hello-fawkes -n my-first-app
    

Rebasing in Production

In a mature platform, rebasing is automated: - CI/CD pipeline runs pack rebase nightly - Images get security updates without code changes - ArgoCD deploys updated images automatically

This is how you patch 100 microservices in minutes, not weeks.

Checkpoint

You've rebased your image and understand the power of this feature!

Step 9: Remove the Dockerfile

Now that you're using Buildpacks, the Dockerfile is obsolete.

  1. Remove the Dockerfile:
rm Dockerfile
  1. Optional: Add a project.toml for Buildpack configuration:
[_]
schema-version = "0.2"

[[io.buildpacks.build.env]]
name = "BP_NODE_VERSION"
value = "18"

[[io.buildpacks.build.env]]
name = "BP_NPM_INSTALL_ARGS"
value = "--production"
  1. Update your CI/CD pipeline to use pack build instead of docker build.

Example Jenkins pipeline snippet:

stage('Build Image') {
  steps {
    sh '''
      pack build ${IMAGE_NAME}:${VERSION} \
        --builder paketobuildpacks/builder:base \
        --publish
    '''
  }
}
  1. Commit the changes:
    git add Dockerfile project.toml
    git commit -m "Remove Dockerfile, migrate to Buildpacks"
    git push
    

Checkpoint

You've fully migrated from Dockerfiles to Cloud Native Buildpacks!

What You've Accomplished

Congratulations! You've successfully:

  • ✅ Understood the benefits of Buildpacks over Dockerfiles
  • ✅ Built a container image using Cloud Native Buildpacks
  • ✅ Deployed a Buildpack-built image to Fawkes
  • ✅ Learned about rebasing for fast security updates
  • ✅ Migrated away from manual Dockerfile maintenance

Benefits You've Gained

  1. Automated Security - Base images update automatically
  2. Consistency - All Node.js apps use the same buildpack
  3. Best Practices - Non-root user, minimal layers, optimal caching
  4. Fast Patching - Rebase 100 images in minutes
  5. Less Maintenance - No Dockerfiles to update across teams

What's Next?

Continue your Fawkes journey:

  1. Create Golden Path Template - Make Buildpacks the default for new services
  2. Measure DORA Metrics - Track deployment frequency improvements
  3. Buildpacks Philosophy - Deep dive into the trade-offs

Troubleshooting

Pack Build Fails to Detect Buildpack

# Ensure package.json is in the root directory
ls -la package.json

# Try specifying the buildpack explicitly
pack build myimage --buildpack paketo-buildpacks/nodejs

Image Larger Than Expected

  • Buildpacks use Ubuntu, not Alpine
  • Includes build tools for maximum compatibility
  • Trade-off: Larger size for better security and maintainability
  • Consider multi-stage builds if size is critical

Application Won't Start

# Check the buildpack-detected start command
pack inspect YOUR-USERNAME/hello-fawkes:v4.0.0-buildpack

# Verify your package.json "start" script
cat package.json | grep -A2 scripts

# Test locally first
docker run -it YOUR-USERNAME/hello-fawkes:v4.0.0-buildpack

Need to Customize Build Process

  • Use project.toml for buildpack configuration
  • Set environment variables: --env BP_NODE_VERSION=18
  • Add build-time arguments: --env BP_BUILD_ARGS="--verbose"
  • See Paketo Node.js Buildpack docs

Learn More

Feedback

How was your experience migrating to Buildpacks? Did you encounter any issues? Share your feedback in the Fawkes Community Mattermost!