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.
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.
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.
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:
# build.sh
#!/bin/bash
set -e
npm install
npm run lint
npm test
npm run buildThen call it from Jenkins:
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
| Aspect | Buildpacks | Manual Pipelines |
|---|---|---|
| Setup speed | Fast | Slower |
| Control | Limited | Full |
| Debugging | Opaque | Transparent |
| Flexibility | Moderate | High |
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.