top of page

Jenkins Scripted Pipeline Example: Python, GitHub and Docker

Updated: 16 hours ago

A Jenkins scripted pipeline is useful when a delivery workflow needs Groovy logic, dynamic branches or custom control flow that is awkward to express in a purely declarative Jenkinsfile. This tutorial builds a practical continuous-integration pipeline for a small Python Flask service: Jenkins checks out code from GitHub, creates an isolated environment, installs dependencies, runs tests, builds a Docker image and optionally pushes it to a registry.

The example is small enough to teach, but it applies production-minded practices: the Jenkinsfile lives in source control, secrets remain in Jenkins Credentials, stages are visible in build history, failed tests stop later work, and cleanup runs even after errors. Commands assume a Linux Jenkins agent with Git, Python 3 and Docker available.

Accuracy note: This tutorial was reviewed against official Jenkins, Docker and GitHub documentation on October 14, 2026. NeoAI Tech has no sponsorship or affiliate relationship with these tools. Validate plugins, permissions and deployment controls in a non-production environment first.

What is a Jenkins scripted pipeline?

Jenkins supports Declarative and Scripted Pipeline syntax. Declarative Pipeline uses structured pipeline, agent, stages and steps blocks. Scripted Pipeline is a limited Groovy-based domain-specific language and normally begins with node { ... }. The node step schedules work on an executor and allocates a workspace; stage blocks divide the workflow into readable units.

Declarative syntax is usually easier to read and govern. Scripted syntax is appropriate when a pipeline genuinely needs runtime loops, conditional stage creation, reusable Groovy functions or custom exception handling. The extra flexibility also increases review and maintenance cost.

Jenkins recommends storing the workflow as a Jenkinsfile in source control instead of entering it only in the web UI. See the official Pipeline guide and getting-started documentation.

Demo project structure

jenkins-python-demo/
├── app.py
├── test_app.py
├── requirements.txt
├── Dockerfile
├── .dockerignore
└── Jenkinsfile

Flask application

from flask import Flask, jsonify

app = Flask(__name__)

@app.get('/health')
def health():
    return jsonify(status='ok'), 200

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)

Test and dependencies

# requirements.txt
Flask==3.1.2
pytest==8.4.2

# test_app.py
from app import app

def test_health():
    client = app.test_client()
    response = client.get('/health')
    assert response.status_code == 200
    assert response.get_json() == {'status': 'ok'}

Pin versions that the team has tested. A real project should also use an automated dependency-update process and vulnerability scanning instead of keeping tutorial versions indefinitely.

Dockerfile

FROM python:3.13-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY app.py .
EXPOSE 5000
CMD ["python", "app.py"]

# .dockerignore
.git
.venv
__pycache__
.pytest_cache
*.pyc

For production, run as a non-root user, govern base-image updates, scan the image and use a production WSGI server. Those changes are separate from learning the pipeline flow.

Complete Jenkins scripted pipeline example

Save this file as Jenkinsfile at the repository root. Replace your-registry with the real registry namespace. If continuous integration is the only goal, remove the Push Image stage.

node('linux && docker') {
    def imageName = 'your-registry/jenkins-python-demo'
    def imageTag = env.BUILD_NUMBER

    try {
        stage('Checkout') {
            checkout scm
        }

        stage('Install Dependencies') {
            sh '''
                python3 -m venv .venv
                . .venv/bin/activate
                python -m pip install --upgrade pip
                pip install -r requirements.txt
            '''
        }

        stage('Test') {
            sh '''
                . .venv/bin/activate
                pytest -q
            '''
        }

        stage('Build Docker Image') {
            withEnv(["IMAGE_NAME=" + imageName, "IMAGE_TAG=" + imageTag]) {
                sh 'docker build --pull -t $IMAGE_NAME:$IMAGE_TAG .'
                sh 'docker tag $IMAGE_NAME:$IMAGE_TAG $IMAGE_NAME:latest'
            }
        }

        stage('Push Image') {
            withEnv(["IMAGE_NAME=" + imageName, "IMAGE_TAG=" + imageTag]) {
                withCredentials([usernamePassword(
                    credentialsId: 'docker-registry-credentials',
                    usernameVariable: 'REGISTRY_USER',
                    passwordVariable: 'REGISTRY_PASSWORD'
                )]) {
                    sh '''
                        echo "$REGISTRY_PASSWORD" | docker login -u "$REGISTRY_USER" --password-stdin
                        docker push "$IMAGE_NAME:$IMAGE_TAG"
                        docker push "$IMAGE_NAME:latest"
                        docker logout
                    '''
                }
            }
        }

        currentBuild.result = 'SUCCESS'
    } catch (err) {
        currentBuild.result = 'FAILURE'
        throw err
    } finally {
        sh 'docker logout || true'
        deleteDir()
    }
}

Never place real usernames, passwords, API tokens or private keys directly in the Jenkinsfile. The credential ID is not the secret; it is a reference to a protected credential stored by Jenkins.

How the pipeline works

Checkout and workspace

checkout scm reads the source configuration attached to the job. In a Multibranch Pipeline, Jenkins can discover branches and pull requests and load the Jenkinsfile from each branch. This lets pipeline changes follow the same pull-request review process as application code.

Isolated dependency installation

The pipeline creates .venv inside the workspace. A fresh environment reduces accidental reliance on packages already installed on the agent. Each sh invocation starts a new shell, which is why the test stage activates .venv again.

Test gate

pytest returns a non-zero exit code when a test fails. Jenkins treats the sh step as failed, exits the try block, marks the build as FAILURE and runs the finally block. The image is not built or pushed when the test gate fails.

Docker image tags

The image receives an immutable build-number tag and a convenient latest tag. Deployments should normally use the immutable tag—or preferably a digest—so operators know exactly which artifact is running. The --pull option checks for a newer base image with the same tag, but it does not make a floating tag reproducible.

Credentials and cleanup

withCredentials temporarily binds the registry username and password to environment variables. Jenkins masks supported secrets in logs, but masking is not a complete security boundary. Do not enable shell tracing, print environment variables or expose credentials to untrusted pull-request code. The finally block logs out and deletes the workspace even after a failure.

Jenkins documents credential bindings and the Snippet Generator in Using a Jenkinsfile. Its credentials guide explains credential types and protected storage.

Configure Jenkins step by step

  1. Confirm the Pipeline, Git and Credentials Binding plugins. Install Docker Pipeline only when using docker.image(...).inside or other Docker-specific Pipeline steps.

  2. Prepare a Linux agent labeled linux and docker. Confirm that the Jenkins agent user can run Docker using the least privilege your design supports.

  3. Add the registry username and short-lived token as a Username with password credential. Set its ID to docker-registry-credentials or change the Jenkinsfile reference.

  4. Create a Multibranch Pipeline, or create a Pipeline job and choose Pipeline script from SCM. Select Git and enter the repository URL and source credential.

  5. Set the Script Path to Jenkinsfile, save, and run Build Now.

  6. Inspect Console Output and Stage View. Confirm that a failed test blocks the image stages and that no secret value appears in logs.

Separate build credentials from deployment credentials. A build job usually needs permission to push only to one image repository, not permission to change production infrastructure. Restrict job configuration and credential use by folder and role.

Trigger Jenkins from GitHub with a webhook

A webhook delivers an event when code changes, avoiding periodic polling. GitHub lists triggering external CI systems such as Jenkins as a common webhook use case.

  1. Expose a secured HTTPS endpoint that GitHub can reach. Avoid exposing the full Jenkins administration interface unnecessarily.

  2. Install and configure the GitHub integration required by the Jenkins job type.

  3. In GitHub, open the repository’s Settings, choose Webhooks, and add the Jenkins endpoint supplied by the integration.

  4. Use application/json, configure a webhook secret when supported, and subscribe only to required push or pull-request events.

  5. Push a harmless commit and check both the GitHub delivery record and the Jenkins build cause.

Use GitHub’s official webhook documentation for event behavior. Validate signatures or shared secrets according to the installed Jenkins integration; receiving an HTTP request alone does not prove GitHub sent it.

Optional: test inside a Docker container

Containers can give the test stage a consistent toolchain. Scripted Pipeline supports docker.image(...).inside when the Docker Pipeline plugin is installed.

node('linux && docker') {
    checkout scm
    docker.image('python:3.13-slim').inside {
        stage('Test') {
            sh 'pip install -r requirements.txt'
            sh 'pytest -q'
        }
    }
}

The official Docker with Pipeline guide notes that these steps require the Docker Pipeline plugin. Pin and review tool images instead of silently accepting every upstream change.

Common errors and fixes

docker: permission denied

The agent cannot access the Docker daemon. Confirm the operating-system user, socket path and permissions. Mounting the host Docker socket gives powerful host-level access, so isolate the agent and treat the configuration as privileged.

pytest: command not found

The virtual environment was not activated in the current shell step, dependency installation failed, or Python is missing. Activate .venv in each separate sh step that needs it.

Could not find credentials entry

The credentialsId does not match an accessible credential. Check the ID, folder scope and credential type. Use Jenkins Pipeline Syntax to generate a withCredentials example instead of guessing binding fields.

Unexpected Groovy or shell interpolation

Groovy interpolates double-quoted strings before the shell runs. Prefer single-quoted shell scripts for commands containing secrets and decide deliberately whether Groovy or the shell should expand each variable.

Webhook succeeds but no build starts

Check the job trigger, repository URL, branch discovery and webhook event type. An HTTP 2xx response means the endpoint answered; it does not guarantee Jenkins matched the event to a job.

Security and reliability checklist

  • Store the Jenkinsfile in source control and require review for pipeline changes.

  • Use isolated or ephemeral agents; do not run normal builds on the controller.

  • Restrict credentials by folder and purpose, rotate them, and prefer short-lived tokens.

  • Prevent secrets from reaching logs, artifacts, caches or Docker layers.

  • Protect branches and require tests before merge.

  • Pin dependencies and govern base-image updates; scan dependencies and built images.

  • Tag artifacts immutably and record the Git commit for each image.

  • Add timeouts and retry only transient operations, not deterministic test failures.

  • Back up Jenkins securely and test controller recovery.

  • Keep Jenkins core and plugins supported and patched; remove unused plugins.

Frequently asked questions

What is the difference between Declarative and Scripted Pipeline?

Declarative Pipeline uses a structured syntax optimized for readability and validation. Scripted Pipeline uses a Groovy-based DSL inside node blocks and offers more direct control over loops, conditions and exception handling. Prefer Declarative unless the workflow genuinely needs Scripted flexibility.

Where should the Jenkinsfile be stored?

Store it in source control, normally at the repository root. Configure the job’s Script Path if another location is used. Source control provides review history, branching and repeatability.

Can Jenkins build Docker images from a container?

Yes, but the architecture matters. Options include a host daemon, Docker-in-Docker or a remote builder. Each has security and isolation trade-offs. Mounting a host Docker socket should be treated as privileged access.

How should secrets be used in Scripted Pipeline?

Create secrets in Jenkins Credentials and reference credential IDs through withCredentials or a generated binding. Never hard-code secrets in the Jenkinsfile, repository, image layers or logs.

Should a pipeline push the latest tag?

It can publish latest for convenience, but deployments should use an immutable build tag or image digest. A mutable tag does not identify the exact artifact running.


Move from the demo to a reliable delivery pipeline

Begin with checkout, install and test. Add the Docker build after tests are stable, then add registry publishing with narrowly scoped credentials. Treat deployment as a separate reviewed stage with environment protection rather than an automatic consequence of every branch build.

NeoAI Tech helps teams design CI/CD workflows, containerize applications and prepare cloud deployments with practical documentation and training. The goal is a pipeline the team can understand, audit and recover—not merely a green build badge.

Need cloud architecture or migration support for a delivery pipeline like this? Explore Cloud Engineering services.


$50

Product Title

Product Details goes here with the simple product description and more information can be seen by clicking the see more button. Product Details goes here with the simple product description and more information can be seen by clicking the see more button

$50

Product Title

Product Details goes here with the simple product description and more information can be seen by clicking the see more button. Product Details goes here with the simple product description and more information can be seen by clicking the see more button.

$50

Product Title

Product Details goes here with the simple product description and more information can be seen by clicking the see more button. Product Details goes here with the simple product description and more information can be seen by clicking the see more button.

Recommended Products For This Post

Recent Posts

See All

Comments


bottom of page
`; },400); })();