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:
- ✅ Understood the benefits of Cloud Native Buildpacks over Dockerfiles
- ✅ Removed your Dockerfile and configured Buildpacks
- ✅ Built a container image using Buildpacks
- ✅ Deployed the Buildpack-built image to Fawkes
- ✅ Verified automatic base image updates (rebasing)
Prerequisites
Before you begin, ensure you have:
- [ ] Completed Tutorial 1: Deploy Your First Service
- [ ] Your
hello-fawkesservice with a Dockerfile - [ ] Docker or Podman installed locally
- [ ]
packCLI installed (installation guide) - [ ] Basic understanding of Docker and container images
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.
- 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"]
-
Identify what the Dockerfile is doing:
-
✅ Choosing a base image (node:18-alpine)
- ✅ Installing dependencies (npm ci)
- ✅ Copying application code
- ✅ Creating a non-root user
-
✅ Setting the start command
-
Problems with this approach:
- Manual updates: When Node.js 18 reaches EOL, you must update it
- CVE lag: Security patches require rebuilding every image
- Inconsistency: Different teams use different base images
- 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.
- 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
- Verify installation:
pack --version
- 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.
- Verify your
package.jsonhas 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"
}
}
- Ensure you have a
package-lock.json:
npm install
- Optional: Create a
.packignorefile to exclude files from the build:
.git/
.gitignore
*.md
Dockerfile
k8s/
node_modules/
- 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!
- Build your application with Pack:
pack build YOUR-USERNAME/hello-fawkes:v4.0.0-buildpack \
--builder paketobuildpacks/builder:base \
--env BP_NODE_VERSION=18
-
Watch the build process:
-
Buildpacks will detect that you're using Node.js
- It will install the correct Node.js version
- It will run
npm cito install dependencies - It will configure the start command
-
It will create a secure, minimal container image
-
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'
- 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.
- Check Dockerfile image size:
docker images YOUR-USERNAME/hello-fawkes:v3.0.0
- Check Buildpack image size:
docker images YOUR-USERNAME/hello-fawkes:v4.0.0-buildpack
- 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.
- 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
- 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"
}
- Check the running user (should be non-root):
docker exec hello-fawkes-test whoami
Should output: cnb (Buildpacks default non-root user)
- 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.
- Push the image to your registry:
docker push YOUR-USERNAME/hello-fawkes:v4.0.0-buildpack
- Update
k8s/deployment.yamlto 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
- Commit and push:
git add k8s/deployment.yaml
git commit -m "Deploy Buildpack-built image"
git push
- Watch the rollout:
kubectl rollout status deployment/hello-fawkes -n my-first-app
- Verify the new pods are running:
kubectl get pods -n my-first-app
- 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.
- Check the current base image:
pack inspect YOUR-USERNAME/hello-fawkes:v4.0.0-buildpack
Note the "Run Image" section.
- Rebase to the latest base image (simulating a security update):
pack rebase YOUR-USERNAME/hello-fawkes:v4.0.0-buildpack
-
Compare the speed:
-
Rebuild: 2-5 minutes (downloads dependencies, runs build)
-
Rebase: 5-10 seconds (only updates base layers)
-
Push the rebased image:
docker push YOUR-USERNAME/hello-fawkes:v4.0.0-buildpack
- 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.
- Remove the Dockerfile:
rm Dockerfile
- Optional: Add a
project.tomlfor 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"
- Update your CI/CD pipeline to use
pack buildinstead ofdocker build.
Example Jenkins pipeline snippet:
stage('Build Image') {
steps {
sh '''
pack build ${IMAGE_NAME}:${VERSION} \
--builder paketobuildpacks/builder:base \
--publish
'''
}
}
- 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
- Automated Security - Base images update automatically
- Consistency - All Node.js apps use the same buildpack
- Best Practices - Non-root user, minimal layers, optimal caching
- Fast Patching - Rebase 100 images in minutes
- Less Maintenance - No Dockerfiles to update across teams
What's Next?
Continue your Fawkes journey:
- Create Golden Path Template - Make Buildpacks the default for new services
- Measure DORA Metrics - Track deployment frequency improvements
- 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.tomlfor 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
- Buildpacks Philosophy - Understand the security vs. control trade-off
- Cloud Native Buildpacks Documentation - Official CNB docs
- Paketo Buildpacks - Enterprise-grade buildpacks for Java, Node.js, Go, Python, and more
Feedback
How was your experience migrating to Buildpacks? Did you encounter any issues? Share your feedback in the Fawkes Community Mattermost!