To automate code integration, testing, and containerized deployment, a CI/CD pipeline with Git, Jenkins, and Docker pulls source from Git, runs builds in Jenkins, and publishes Docker images to a registry. A manual deployment process adds roughly 30 seconds of latency per commit—human response time plus network overhead—which scales to several minutes of idle time per day in a team of ten developers. The guide below eliminates that hidden cost by wiring the three tools together.
📑 Table of Contents
- 🔧 Repository Setup — Why Git Matters
- ⚙️ Jenkins Configuration — How Jenkins Executes Builds
- 🛠️ Install Jenkins and Required Plugins
- 📄 Jenkinsfile — Declarative Pipeline Script
- 🐳 Docker Integration — What Docker Adds
- 📦 Dockerfile — Minimal Python Application
- 🔧 Build and Verify the Image
- 🚀 Pipeline Assembly — Building the CI/CD pipeline with Git Jenkins Docker
- 🔗 Configure Git Webhook
- 📦 Jenkins Job Linking
- 🔐 Credential Management
- 🔐 Securing the Flow — Why Credentials Matter
- 🛡️ Use Jenkins Credentials Binding
- 🟩 Final Thoughts
- ❓ Frequently Asked Questions
- Can I use a different CI server instead of Jenkins?
- Do I need a separate Docker registry?
- How does the pipeline handle failed tests?
- 📚 References & Further Reading
🔧 Repository Setup — Why Git Matters
Git is a distributed version‑control system that records immutable snapshots of the source tree. Each commit is stored as a SHA‑1 object, enabling O(1) lookup and fast branching, merging, and rollback.
$ git init myapp
Initialized empty Git repository in /home/dev/myapp/.git/
$ cd myapp
$ echo "print('Hello CI/CD')" > app.py
$ git add app.py
$ git commit -m "Initial commit"
[master (root-commit) abc1234] Initial commit 1 file changed, 1 insertion(+) create mode 100644 app.py
Creating a .gitignore prevents unwanted files from entering the repository.
# .gitignore
__pycache__/
*.pyc
.env
build/
What this does: (Also read: ⚙️ Building a Jenkins Docker CI CD pipeline tutorial made easy)
-
git init: creates a new repository and allocates a
.gitdirectory for object storage. - git add: stages files by adding their blob hashes to the index.
- git commit: writes a new commit object linking to the index snapshot, enabling reproducible builds.
Key point: The immutable commit hash provides a reliable source identifier for downstream Jenkins jobs.
⚙️ Jenkins Configuration — How Jenkins Executes Builds
Jenkins is a server‑side automation engine that pulls code, runs defined stages, and records results in a centralized UI.
🛠️ Install Jenkins and Required Plugins
On an Ubuntu host, install the LTS package and the Docker Pipeline plugin.
$ sudo apt-get update
$ sudo apt-get install -y openjdk-11-jre
$ wget -q -O - https://pkg.jenkins.io/debian-stable/jenkins.io.key | sudo apt-key add -
$ sudo sh -c 'echo deb https://pkg.jenkins.io/debian-stable binary/ > /etc/apt/sources.list.d/jenkins.list'
$ sudo apt-get update
$ sudo apt-get install -y jenkins
$ sudo systemctl start jenkins
$ sudo systemctl status jenkins
● jenkins.service - Jenkins Continuous Integration Server Loaded: loaded (/lib/systemd/system/jenkins.service; enabled) Active: active (running) since Thu -08-15 10:12:09 UTC; 1min ago
After installation, navigate to http://localhost:8080 to retrieve the initial admin password.
$ sudo cat /var/lib/jenkins/secrets/initialAdminPassword
d1a2b3c4d5e6f7g8h9i0j1k2l3m4n5o6
📄 Jenkinsfile — Declarative Pipeline Script
The pipeline script defines stages that run on a Docker‑enabled agent, guaranteeing that every step executes in the same container environment.
# Jenkinsfile
pipeline { agent { docker { image 'python:3.11-slim' args '-v /var/run/docker.sock:/var/run/docker.sock' } } environment { REGISTRY = 'registry.example.com/myapp' IMAGE_TAG = "${env.BRANCH_NAME}-${env.BUILD_NUMBER}" } stages { stage('Checkout') { steps { checkout scm } } stage('Test') { steps { sh 'python -m unittest discover -s tests' } } stage('Build Image') { steps { sh ''' docker build -t $REGISTRY:$IMAGE_TAG . ''' } } stage('Push Image') { steps { sh ''' docker login -u $DOCKER_USER -p $DOCKER_PASS $REGISTRY docker push $REGISTRY:$IMAGE_TAG ''' } } } post { always { sh 'docker logout $REGISTRY' } }
}
What this does:
- agent docker: runs each stage inside a lightweight container, isolating dependencies from the Jenkins master.
- environment: defines reusable variables for the image name and tag, avoiding hard‑coded strings.
- checkout scm: pulls the exact commit that triggered the build, guaranteeing reproducibility.
- docker build: creates a layered image; each layer is cached based on its checksum, reducing subsequent build time from minutes to seconds.
According to the Jenkins documentation, the pipeline block is evaluated on the master, but sh steps execute on the selected agent, ensuring that build logic runs where the required tools are available.
Key point: Jenkins orchestrates the end‑to‑end flow, turning a Git commit into a Docker image without manual intervention. (More onPythonTPoint tutorials)
🐳 Docker Integration — What Docker Adds
Docker builds immutable images that encapsulate runtime dependencies, providing a consistent execution environment across hosts.
📦 Dockerfile — Minimal Python Application
# Dockerfile
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install -no-cache-dir -r requirements.txt
COPY . .
CMD ["python", "app.py"]
What this does:
- FROM: pulls a base image containing the Python interpreter.
- WORKDIR: sets the working directory, creating it if missing.
-
COPY + RUN pip install: creates a layer that caches dependencies; the layer is rebuilt only when
requirements.txtchanges, which speeds up incremental builds. - CMD: defines the default command executed when a container starts.
🔧 Build and Verify the Image
$ docker build -t myapp:1.0 .
Sending build context to Docker daemon 12.34kB
Step 1/5: FROM python:3.11-slim
3.11-slim: Pulling from library/python
Digest: sha256:7a9b3c4d5e6f...
Status: Downloaded newer image for python:3.11-slim
Step 2/5: WORKDIR /app --> Running in 8c9d7e...
Removing intermediate container 8c9d7e --> 4f2a1b...
Step 3/5: COPY requirements.txt . --> 9b3c4d...
Step 4/5: RUN pip install -no-cache-dir -r requirements.txt --> Running in 1a2b3c...
Collecting flask
...
Successfully built 5d6e7f
Successfully tagged myapp:1.0
Running the image validates the entry point.
$ docker run -rm myapp:1.0
Hello CI/CD
Key point: The Docker image can be moved to any host that runs the Docker engine, guaranteeing identical behavior. (Also read: 🚀 Terraform deploy for Python Flask and Docker made easy)
🚀 Pipeline Assembly — Building the CI/CD pipeline with Git Jenkins Docker
This section connects the Git webhook, Jenkins job, and Docker registry into a single automated flow.
🔗 Configure Git Webhook
In the repository settings, add a webhook that posts to Jenkins whenever a push occurs.
$ curl -X POST -u admin:api_token \ -H "Content-Type: application/json" \ -d '{"name":"web","events":["push"],"config":{"url":"http://jenkins.example.com/github-webhook/","content_type":"json"}}' \ https://git.example.com/api/v4/projects/42/hooks
{"id":101,"url":"http://jenkins.example.com/github-webhook/","push_events":true}
Git sends a JSON payload containing the commit SHA; Jenkins extracts GIT_COMMIT to check out the exact source.
📦 Jenkins Job Linking
In Jenkins, create a Multibranch Pipeline that scans the repository and automatically creates a job for each branch. (Also read: ⚙️ Ansible roles vs playbooks for Docker provisioning — which one should you use?)
$ java -jar jenkins-cli.jar -s http://localhost:8080 create-job myapp-multibranch < myapp-config.xml
Job created
Sample myapp-config.xml (excerpt):
# myapp-config.xml
myorg myapp github-token
When a push arrives, Jenkins triggers the pipeline defined in Jenkinsfile, which builds and pushes the Docker image.
🔐 Credential Management
Store Docker registry credentials as Jenkins secret text variables to avoid leaking passwords.
$ java -jar jenkins-cli.jar -s http://localhost:8080 create-credentials-domain \ -domain docker-creds \ -username $DOCKER_USER \ -password $DOCKER_PASS
Created credentials
These variables are referenced in the Push Image stage, ensuring that the registry login occurs only at runtime.
Key point: The assembled pipeline delivers a reproducible artifact from source commit to Docker image with zero manual steps.
🔐 Securing the Flow — Why Credentials Matter
Secure handling of secrets prevents unauthorized image pushes and protects the source repository.
🛡️ Use Jenkins Credentials Binding
# credentials-binding.xml
DOCKER_USER DOCKER_USER DOCKER_PASS docker-registry-creds
What this does:
- SecretBuildWrapper: injects the credentials into the build environment only for the duration of the job.
-
UsernamePasswordBinding: maps the stored secret to environment variables accessed by the
docker logincommand.
By keeping secrets out of the workspace and logs, the pipeline complies with best‑practice security models.
Key point: Credential binding isolates sensitive data, reducing attack surface without altering pipeline logic.
🟩 Final Thoughts
The CI/CD pipeline composed of Git, Jenkins, and Docker demonstrates how a single commit can travel through version control, automated testing, image creation, and registry publication without manual intervention. Git supplies an immutable source reference, Jenkins provides orchestrated execution with built‑in retry logic, and Docker delivers a portable runtime artifact. Combined, the overall latency drops from minutes of manual effort to seconds of automated processing, freeing developer time for feature work instead of release mechanics.
Each image tag encodes the originating branch and build number (e.g., feature‑login-42), creating an audit trail that enables rapid rollback if a regression is detected. The pipeline can be extended with additional stages—security scanning, canary deployments, or performance testing—making it a flexible foundation for modern software delivery.
❓ Frequently Asked Questions
Can I use a different CI server instead of Jenkins?
Yes. Any CI system that can execute a Jenkinsfile-compatible script (for example, GitHub Actions or GitLab CI) can replace Jenkins, but you must adapt the webhook configuration and credential handling to the new server's API.
Do I need a separate Docker registry?
A registry is optional for local testing; however, a private registry such as Docker Hub, GitHub Packages, or a self‑hosted Harbor instance provides versioned storage and access control, which is essential for production deployments.
How does the pipeline handle failed tests?
If the Test stage exits with a non‑zero status, Jenkins marks the build as failed and aborts subsequent stages, preventing a faulty image from being pushed.
💡 Want to practise this hands-on? DigitalOcean gives new accounts $200 free credit for 60 days — enough to spin up a full Linux/Docker/Kubernetes environment at no cost.
📚 Recommended reading: Best DevOps & cloud books on Amazon — from Linux fundamentals to Kubernetes in production, curated for working engineers.
📚 References & Further Reading
- Official Git documentation — fundamentals of version control: git-scm.com
- Jenkins Pipeline documentation — declarative syntax and best practices: jenkins.io
- Dockerfile reference — building container images efficiently: docs.docker.com
Top comments (0)