Cloud Platform Engineering

Eliminating Dockerfiles with Cloud Native Buildpacks

How we replaced dozens of hand-maintained Dockerfiles with a single pack build command — standardizing container creation for every AI microservice across GKE and Cloud Run.

8 min read
The Dockerfile Maintenance Tax

When you're running 20+ AI microservices, each with its own Dockerfile, maintenance becomes a tax. Security patches to the base OS? Update 20 Dockerfiles. Python version bump? 20 Dockerfiles. Multi-stage build optimization? You get the idea. Worse, each Dockerfile is slightly different — some use slim, some alpine, some copy requirements before source, some don't. Inconsistency breeds security vulnerabilities, bloating images and increasing build time.

Cloud Native Buildpacks: Convention Over Configuration

Cloud Native Buildpacks solve this by auto-detecting the application framework (Python + requirements.txt or pyproject.toml), installing dependencies, and packaging everything into a secure OCI image. The developer writes zero container configuration. For web services, the default entrypoint is Gunicorn. For Cloud Run Jobs that need a custom command, a simple Procfile overrides it. This shifts the operational burden away from application developers.

groovy
// pipelines/utilities/packBuildPush.groovy
def call(Map config=[:]) {
    def fullImageName = "${config.dockerPushRegistry.name}/${config.dockerPushRegistry.repo}/${config.appName}:${config.appFullVersion}"
    
    // A single, clean command. No Dockerfile needed.
    // The buildpack reads project.toml for build-time config.
    def command = "pack build \"${fullImageName}\" --builder \"${config.builderImage}\" --publish"
    
    echo "Building: ${command}"
    sh command
}
Why This Matters for Security

Buildpacks create a strict separation between the OS layer and the application layer. When a critical CVE hits the base OS, the platform team can rebase every container image without rebuilding application code — something impossible with traditional Dockerfiles where OS and app layers are entangled. Every container gets the same security patches, the same OS configuration, and the same best practices automatically. This is a game-changer for enterprise compliance audits.

Buildpacks separate the concern of "how to package the code" from the code itself — letting platform teams own security while developers own features.
Lessons Learned: Caching Python Virtual Environments

A major gotcha when adopting Buildpacks in Jenkins is dependency caching. Since Buildpacks execute containerized build steps, they create internal volumes to cache downloaded pip packages. In a clean Jenkins runner environment, these caches are wiped out between builds, causing pack to re-download heavy libraries (like PyTorch or TensorFlow) every single time, inflating build times from 3 minutes to over 30 minutes! The lesson: configure persistent named volumes or persistent host directories in your Jenkins cluster and bind them to the Pack cache paths (/home/cnb/.cache) to preserve dependency caches across pipeline runs.

More Recent Posts