How I Built a Local CI/CD Pipeline with Jenkins, GitHub Webhooks & ngrok (Beginner-Friendly)

Adheeb AnvarAdheeb Anvar
6 min read

Jenkins

Why this is worth your time (and why most tutorials suck)

Most CI/CD guides assume you run Jenkins on a cloud VM or use GitHub Actions. That's fine , but if you're learning, you want full control and zero cloud bills. Running Jenkins locally teaches the basics first. Once you understand that, moving to the cloud will become very easy and efficient.


What you'll build

  • Jenkins running locally (Docker recommended)

  • Ngrok exposing Jenkins to the internet

  • A GitHub webhook pointing to ngrok

  • A Jenkinsfile in your repo that runs automatically on push


Prerequisites

  • Docker (for Jenkins) or Jenkins installed locally

  • GitHub repo you control

  • ngrok account (free tier works)

  • Basic terminal + Git knowledge


Quick architecture (one-liner)

GitHub (push) → webhook → ngrok → local Jenkins → runs Jenkinsfile (Build → Test → Deploy)


Step 0 — Quick setup checklist (commands you'll need)

# start Jenkins in Docker (recommended)
docker run -p 8080:8080 -p 50000:50000 -d \
  -v jenkins_home:/var/jenkins_home \
  jenkins/jenkins:lts-jdk17

# start ngrok to expose Jenkins
ngrok http 8080

Step 1 — Start Jenkins locally

  1. Run the docker run command above. (Yes, you need the volume so your Jenkins data survives restarts.)

  2. Open http://localhost:8080 and finish initial setup (install suggested plugins, create admin user).

  3. Make sure Git, GitHub, and Pipeline related plugins are installed:

    • Git Plugin

    • GitHub Plugin

    • GitHub Branch Source (if using multibranch)

    • Pipeline: Multibranch (or Pipeline if single-branch)

Common gotcha: Jenkins ignores ‘jenkinsfile’ lowercase — the file must be called Jenkinsfile (capital J).


Step 2 — Expose Jenkins with ngrok

GitHub cannot reach localhost. ngrok creates a public URL that tunnels to your machine.

  1. Download & sign up: go to ngrok.com and get your auth token.

  2. Authenticate and run:

     ngrok config add-authtoken <YOUR_TOKEN>
     ngrok http 8080
    
  3. Note the https://xxxxxx.ngrok-free.app forwarding URL. That's your public Jenkins address.

Important: ngrok free URLs are temporary, they change each run. Expect to update the webhook when the URL changes.


Step 3 — Add GitHub webhook

  1. In your GitHub repo → Settings → Webhooks → Add webhook.

  2. Payload URL: https://<your-ngrok-id>.ngrok-free.app/github-webhook/

  3. Content type: application/json

  4. Events: select Just the push event

  5. Save.

Test: GitHub will send a ping event immediately. Check the webhook's Recent Deliveries — you should see a 200 response if Jenkins/ngrok are reachable.

Common fail: If GitHub shows Content type:form or delivery status "failed", fix content type to application/json and ensure ngrok is running.


Step 4 — Create your Jenkins job (multibranch or pipeline)

You have two realistic choices:

  • Jenkins dashboard → New ItemMultibranch Pipeline → name it.

  • Branch Sources → Add → Git or GitHub → paste repo URL.

  • Add credentials if your repo is private (Personal Access Token recommended).

  • Save.

Jenkins will scan branches and create per-branch pipelines automatically when it finds a Jenkinsfile in branch roots.

B. Single Pipeline (if you only care about main)

  • New Item → Pipeline → Pipeline definition from SCM → Git → repo URL, credentials, script path Jenkinsfile.

Note: For multibranch, you don't tick "Pipeline script from SCM" — Jenkins manages the Jenkinsfile discovery itself.


Step 5 — Add a Jenkinsfile to your repo

Place Jenkinsfile at the root of the branch (e.g., main). Example (Declarative — recommended):

pipeline {
  agent any

  stages {
    stage('Greet') {
      steps { echo 'Hello world' }
    }

    stage('Build') {
      steps { sh 'echo Building...' }
    }

    stage('Test') {
      steps { sh 'echo Running tests...' }
    }

    stage('Deploy') {
      steps { sh 'echo Deploying...' }
    }
  }

  post {
    success { echo 'Pipeline Success' }
    failure { echo 'Pipeline Failed' }
  }
}

Step 6 — Wire Jenkins to GitHub (root URL & plugins)

  1. Jenkins → Manage Jenkins → Configure System → set Jenkins URL to your ngrok URL (e.g., https://abcd.ngrok-free.app/). This lets Jenkins build correct webhook links.

  2. Ensure GitHub plugin and GitHub Branch Source are installed.

  3. For Multibranch: under the job's config, add the GitHub repo as Branch Source and set credentials.

  4. Enable Scan Multibranch Pipeline Triggers (use webhook as primary; schedule a short periodic scan like @hourly or 1 min as fallback).


Step 7 — Test: push code and watch it run

  1. Commit a trivial change to README.md (or update Jenkinsfile) and git push.

  2. Watch ngrok terminal — you'll see a POST /github-webhook/ hit.

  3. Jenkins should now see the webhook and run a build. In Jenkins:

    • Open job → click the branch → click latest build (#1) → Console Output.

    • You'll see the echoi and sh outputs for each stage.


What I learned (short & honest)

  • Local-first CI/CD is perfect for learning: you see every step.

  • ngrok is the fastest way to expose local services for webhooks.

  • Jenkins is powerful but picky: file names, pipeline syntax, and plugins all matter.

  • Multibranch pipelines remove manual job creation; Jenkinsfile is the very easy to create.


Next practical steps (what I'd do next)

  • Add real tests (Pytest, Jest, JUnit) into the Test stage and fail the build if tests fail.

  • Use Docker agent in pipeline: run stages inside containers for clean builds.

  • Push Docker images to Docker Hub from Jenkins.

  • Migrate Jenkins from local/ngrok to a small VPS or cloud VM when you want a persistent endpoint.

  • Add notifications (Slack / Email) and artifact archiving.


FAQ — Jenkins + ngrok + GitHub Webhook (quick, practical answers)

1: What is a Jenkinsfile and why do I need it?

A: Jenkinsfile is the pipeline-as-code file that tells Jenkins what to run (stages, steps, environment, post actions). Put it in your repo so Jenkins can version-control the build logic with your code.

2: Does Jenkinsfile have to live in the repo root?

A: By default, yes. Jenkins looks for Jenkinsfile at the branch root. You can keep it in a subfolder (e.g., ci/Jenkinsfile) but must update the Script Path (or multibranch source) to point to that path.

3: What is the difference between Multibranch Pipeline and Pipeline job?

A: Multibranch Pipeline: Jenkins auto-scans branches and runs a pipeline for each branch that contains a Jenkinsfile. Whereas Single Pipeline job: Tied to one branch/script path. Simpler for single-branch projects.

4: Is ngrok safe to use? Any alternatives?

A: Ngrok is fine for local dev/testing. It exposes a public endpoint to your machine, so treat it like a public service. Alternatives: Cloudflare Tunnel, LocalTunnel, FRP (self-hosted). For production use a proper cloud Jenkins instance or VPN-protected tunnel.

5: My webhook shows ping OK but no build runs — why?

A:ping only verifies delivery. After that: ensure webhook event is push, content-type application/json, and Jenkins is configured for that repo (branch source with credentials). Also inspect Jenkins Scan Multibranch Pipeline Log for errors.

6: Where’s The code?

A:https://github.com/adheeb2/learn-ci-cd-with-jenkins-docker


Built by Adheeb Anvar — Conversational AI Developer

0
Subscribe to my newsletter

Read articles from Adheeb Anvar directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Adheeb Anvar
Adheeb Anvar

I’m a backend engineer diving deep into conversational AI — building chatbots and NPCs that don’t just reply, but remember, react, and feel alive. Most of my projects start with solid backends — NestJS, FastAPI, Go, Postgres, Redis — but my focus is on turning those systems into the foundation for more human-like AI interactions. This is where I share that journey: the experiments, the missteps, and the wins. From procedural NPC dialogue to multi-agent AI systems, I’m documenting the whole process here