Devops

Running Jenkins Pipelines Without Buildpacks: A Practical Guide

April 7, 2026
Published
#Automation#CI/CD#DevOps#Docker#Jenkins

Buildpacks are convenient—until they aren’t. They abstract away build logic, detect runtimes, and package applications automatically. But that convenience can become a limitation when you need fine-grained control, custom tooling, or predictable builds.

That’s where running Jenkins pipelines without buildpacks becomes a practical choice. Instead of relying on opinionated automation, you explicitly define every step of your build and deployment process.

Why skip buildpacks?

Here’s the thing: buildpacks are great for standard use cases, but real-world systems rarely stay “standard” for long.

  • Full control over dependencies and build steps
  • Predictable builds without hidden detection logic
  • Custom workflows tailored to your stack
  • Easier debugging when something breaks

A common mistake developers make is assuming buildpacks simplify everything forever. In reality, once your pipeline grows complex, abstraction becomes friction.

Starting with a simple Jenkinsfile

Let’s break this down with a minimal pipeline that builds a Node.js app—no buildpacks involved.

JSON
1pipeline {
2  agent any
3
4  stages {
5    stage('Checkout') {
6      steps {
7        git 'https://github.com/example/node-app.git'
8      }
9    }
10
11    stage('Install Dependencies') {
12      steps {
13        sh 'npm install'
14      }
15    }
16
17    stage('Run Tests') {
18      steps {
19        sh 'npm test'
20      }
21    }
22
23    stage('Build') {
24      steps {
25        sh 'npm run build'
26      }
27    }
28  }
29}

No detection logic. No magic. Just explicit commands.

Adding Docker for consistency

Here’s where things get interesting. Instead of relying on the Jenkins host environment, you can use Docker to ensure consistent builds across environments.

JSON
1pipeline {
2  agent {
3    docker {
4      image 'node:18'
5    }
6  }
7
8  stages {
9    stage('Install') {
10      steps {
11        sh 'npm ci'
12      }
13    }
14
15    stage('Test') {
16      steps {
17        sh 'npm test'
18      }
19    }
20  }
21}

This approach replaces buildpacks with a controlled runtime image. You decide the exact Node version, OS, and dependencies.

Why Docker beats buildpacks here

  • No guessing runtime versions
  • No hidden dependency resolution
  • Reusable across pipelines

Handling multi-step builds

For more complex applications—say a backend and frontend—you can orchestrate multiple steps manually.

Terminal
pipeline {
  agent any

  stages {
    stage('Backend Build') {
      steps {
        dir('backend') {
          sh 'mvn clean package'
        }
      }
    }

    stage('Frontend Build') {
      steps {
        dir('frontend') {
          sh 'npm install'
          sh 'npm run build'
        }
      }
    }

    stage('Package') {
      steps {
        sh 'mkdir -p dist'
        sh 'cp backend/target/app.jar dist/'
        sh 'cp -r frontend/build dist/public'
      }
    }
  }
}

This level of control is difficult to achieve with buildpacks, which typically expect a single app structure.

Custom scripts over conventions

Instead of relying on buildpack conventions, many teams move logic into version-controlled scripts.

Example:

Terminal
# build.sh
#!/bin/bash
set -e

npm install
npm run lint
npm test
npm run build

Then call it from Jenkins:

TEXT
1stage('Build') {
2  steps {
3    sh './build.sh'
4  }
5}

This keeps your pipeline clean and your build logic portable.

Where this approach shines

Skipping buildpacks isn’t just about control—it unlocks flexibility in several scenarios:

  • Polyglot applications (Java + Node + Python)
  • Legacy systems with unusual build steps
  • Security-sensitive environments requiring explicit dependencies
  • Performance tuning at the build level

Gotchas to watch for

Going buildpack-free does come with responsibility.

  • Environment drift: mitigate with Docker or pinned versions
  • Longer setup time: you define everything manually
  • Maintenance overhead: scripts must stay updated

If you ignore these, your pipeline can become fragile instead of flexible.

A quick comparison

AspectBuildpacksManual Pipelines
Setup speedFastSlower
ControlLimitedFull
DebuggingOpaqueTransparent
FlexibilityModerateHigh

When to choose this approach

If your pipeline needs customization, reproducibility, or multi-service coordination, skipping buildpacks is often the better path.

On the other hand, if you’re building simple apps with standard stacks, buildpacks can still save time.

Final thought

Running Jenkins pipelines without buildpacks is less about rejecting automation and more about choosing the right level of abstraction. When you own the build steps, you gain clarity—and that clarity pays off when systems grow complex.

If your current pipeline feels like a black box, this approach might be exactly what you need.

Comments

Leave a comment on this article with your name, email, and message.

Loading comments...

Similar Articles

More posts from the same category you may want to read next.

Share: