Devops

Limiting Serverless Deployments to Pull Requests with Jenkins

April 7, 2026
Published
#CI/CD#DevOps#Jenkins#Pull Requests#Serverless

Uncontrolled serverless deployments can quietly burn through budgets and introduce risk into environments you didn’t intend to touch. If every push to every branch triggers a deployment, you’re essentially treating your cloud infrastructure like a sandbox — even when it isn’t.

One practical guardrail is simple: only deploy serverless applications when a pull request is opened or updated. Jenkins makes this surprisingly flexible if you wire your pipeline correctly.

Why restrict serverless deployments to pull requests?

Before diving into Jenkins specifics, it helps to understand the motivation:

  • Cost control: Serverless deployments (especially with preview environments) can multiply quickly.
  • Safer testing: PRs represent code that’s under review — not experimental commits.
  • Cleaner environments: Avoid polluting staging or preview stacks with every push.
  • Better auditability: Every deployment maps to a reviewable change.

In short, you align infrastructure changes with your code review process.

How Jenkins sees pull requests

Here’s where things get interesting. Jenkins doesn’t inherently “understand” pull requests unless you’re using integrations like:

  • GitHub Branch Source Plugin
  • Bitbucket Branch Source Plugin
  • Multibranch Pipelines

When configured correctly, Jenkins exposes environment variables such as:

  • CHANGE_ID
  • CHANGE_BRANCH
  • CHANGE_TARGET

If CHANGE_ID exists, you’re inside a pull request build. That becomes our key condition.

A minimal Jenkinsfile example

Let’s start with a straightforward pipeline that deploys only when a PR is detected:

Terminal
pipeline {
  agent any

  stages {
    stage('Install Dependencies') {
      steps {
        sh 'npm install'
      }
    }

    stage('Run Tests') {
      steps {
        sh 'npm test'
      }
    }

    stage('Deploy Serverless') {
      when {
        expression {
          return env.CHANGE_ID != null
        }
      }
      steps {
        sh 'npx serverless deploy --stage pr-${env.CHANGE_ID}'
      }
    }
  }
}

This simple condition ensures deployments only happen for pull request builds. Regular branch pushes will skip the deployment stage entirely.

Adding more control (because you probably need it)

In real-world pipelines, you rarely want just “PR = deploy.” You might want:

  • Only deploy PRs targeting main or develop
  • Skip drafts
  • Limit deployments to certain directories

Here’s a more refined condition:

TEXT
1stage('Deploy Serverless') {
2  when {
3    allOf {
4      expression { env.CHANGE_ID != null }
5      expression { env.CHANGE_TARGET == 'main' }
6    }
7  }
8  steps {
9    sh 'npx serverless deploy --stage pr-${env.CHANGE_ID}'
10  }
11}

This ensures deployments only occur for pull requests merging into main, which is often your production-bound branch.

Preview environments per pull request

A common pattern is spinning up isolated environments per PR. This is where serverless shines — you can create lightweight, temporary stacks.

Example:

TEXT
1npx serverless deploy \
2  --stage pr-${CHANGE_ID} \
3  --region us-east-1

This results in environments like:

  • pr-101
  • pr-102

Each PR gets its own deployment without interfering with others.

Cleaning up after merge

A common mistake developers make is forgetting teardown logic. These environments don’t disappear automatically.

You can add a cleanup job triggered on PR close:

TEXT
1pipeline {
2  agent any
3
4  stages {
5    stage('Remove PR Environment') {
6      when {
7        expression { env.CHANGE_ID != null }
8      }
9      steps {
10        sh 'npx serverless remove --stage pr-${env.CHANGE_ID}'
11      }
12    }
13  }
14}

This keeps your cloud account tidy and avoids unnecessary charges.

Guarding against accidental deployments

Even with conditions, there are edge cases:

  • Manual Jenkins builds
  • Misconfigured webhooks
  • Replayed builds

To tighten things further, you can combine conditions with branch checks:

TEXT
1when {
2  allOf {
3    expression { env.CHANGE_ID != null }
4    not {
5      branch 'main'
6    }
7  }
8}

This ensures your deployment never runs on direct commits to main.

Using Jenkins Multibranch Pipeline correctly

If you’re not using Multibranch Pipelines, you’re making this harder than it needs to be.

With Multibranch:

  • PRs are automatically discovered
  • Separate pipelines run per branch/PR
  • Environment variables are injected cleanly

Without it, you’ll need custom scripting to detect PRs — which is brittle and unnecessary.

Performance and cost implications

Restricting deployments to pull requests has a noticeable impact:

  • Fewer deployments = lower Lambda/API Gateway churn
  • Reduced build times in Jenkins
  • Less noise in monitoring systems

But there’s a tradeoff: you lose early deployment feedback from regular commits. Some teams address this by:

  • Running lightweight validations on push
  • Reserving full deployments for PRs

A pattern that works well in practice

One effective setup looks like this:

  • Push to branch → run lint + tests
  • Open PR → deploy preview environment
  • Merge to main → deploy production

This gives you a clean separation between validation, review, and release.

Final thought

Limiting serverless deployments to pull requests isn’t just a Jenkins trick — it’s a discipline. Jenkins simply enforces it.

Once you tie deployments to PRs, your pipeline becomes more predictable, your infrastructure cleaner, and your cloud bill a lot less surprising.

And if you’ve ever wondered why your AWS bill spiked overnight, there’s a good chance this exact change would have prevented it.

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: