A complete CI/CD pipeline demonstration using Jenkins, ArgoCD, Docker, and Kubernetes
This repository showcases a production-ready DevOps workflow with automated builds, containerization, and GitOps-based deployments.
- Overview
- Architecture
- Prerequisites
- Quick Start
- Detailed Setup
- Pipeline Workflow
- Troubleshooting
- Best Practices
- Contributing
- Additional Resources
- License
This project implements a complete CI/CD pipeline that:
- ✅ Automatically builds Docker images on code commits
- ✅ Pushes images to DockerHub registry
- ✅ Updates Kubernetes manifests with new image tags
- ✅ Deploys applications using GitOps principles via ArgoCD
- ✅ Supports multiple environments (staging, production)
- ✅ Includes monitoring with Prometheus and Grafana
Tech Stack:
- CI/CD: Jenkins
- Containerization: Docker
- Orchestration: Kubernetes
- GitOps: ArgoCD
- Registry: DockerHub
- Monitoring: Prometheus, Grafana, Alertmanager
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ GitHub │─────▶│ Jenkins │────▶│ DockerHub │
│ (Source) │ │ (Build) │ │ (Registry) │
└─────────────┘ └─────────────┘ └─────────────┘
│
│ Update Manifest
▼
┌─────────────┐
│ GitHub │
│ (GitOps) │
└─────────────┘
│
│ Sync
▼
┌─────────────┐ ┌─────────────┐
│ ArgoCD │─────▶│ Kubernetes │
│ (Deploy) │ │ (Runtime) │
└─────────────┘ └─────────────┘
│
▼
┌─────────────┐
│ Monitoring │
│ (Prom/Graf) │
└─────────────┘
Workflow:
- Developer pushes code to GitHub
- Jenkins detects changes via webhook
- Jenkins builds Docker image and pushes to DockerHub
- Jenkins updates Kubernetes manifest with new image tag
- ArgoCD detects manifest changes
- ArgoCD syncs and deploys to Kubernetes cluster
- Monitoring stack tracks application health
Before starting, ensure you have:
- Docker (v24.0+) - Install Docker
- Kubernetes Cluster (v1.26+) - Options:
- Minikube (local testing)
- K3s/K3d (lightweight)
- EKS/GKE/AKS (cloud)
- OpenShift (optional)
- kubectl (v1.26+) - Install kubectl
- Git (v2.34+)
- GitHub account with repository access
- DockerHub account (free tier works)
- Basic understanding of Docker, Kubernetes, and CI/CD concepts
- Minimum: 4GB RAM, 2 CPU cores, 20GB disk space
- Recommended: 8GB RAM, 4 CPU cores, 50GB disk space
- Helm (v3.0+) - For advanced deployments
- ArgoCD CLI - For command-line operations
- ngrok - For local webhook testing
# Clone the repository
git clone https://github.com/eknathdj/devops-argocd-jenkins-app.git
cd devops-sample-app
# Build Docker image locally (optional test)
docker build -t devops-sample-app:local .
# Run locally to test
docker run -p 8080:8080 devops-sample-app:local
# Verify application
curl http://localhost:8080/healthFor full CI/CD setup, continue to Detailed Setup.
Option A: Using Docker Compose (Recommended)
Create jenkins/docker-compose.yml:
version: '3.8'
services:
jenkins:
image: jenkins/jenkins:lts
user: root
ports:
- "8080:8080"
- "50000:50000"
volumes:
- jenkins_home:/var/jenkins_home
- /var/run/docker.sock:/var/run/docker.sock
environment:
- JAVA_OPTS=-Djenkins.install.runSetupWizard=false
command: >
bash -c "
apt-get update &&
apt-get install -y docker.io curl git &&
git config --global --add safe.directory '*' &&
/usr/local/bin/jenkins.sh
"
volumes:
jenkins_home:Start Jenkins:
docker-compose -f jenkins/docker-compose.yml up -dOption B: Using Docker CLI
docker run -d \
--name jenkins \
-p 8080:8080 -p 50000:50000 \
-v jenkins_home:/var/jenkins_home \
-v /var/run/docker.sock:/var/run/docker.sock \
--user root \
jenkins/jenkins:lts
# Install dependencies inside Jenkins container
docker exec -it jenkins bash
apt-get update && apt-get install -y docker.io curl git
git config --global --add safe.directory '*'
exit
docker restart jenkins- Get Initial Admin Password:
docker exec jenkins cat /var/jenkins_home/secrets/initialAdminPassword- Access Jenkins:
- Open browser:
http://localhost:8080 - Paste the admin password
- Click "Install suggested plugins"
- Create your admin user
- Open browser:
Go to Manage Jenkins → Plugins → Available plugins
Install these plugins:
- ✅ Git Plugin (git operations)
- ✅ Pipeline (pipeline support)
- ✅ Docker Pipeline (Docker integration)
- ✅ Credentials Binding (secure credential handling)
- ✅ Workspace Cleanup (cleanWs support)
Click "Install without restart" and wait for completion.
-
Generate GitHub PAT:
- Go to GitHub → Settings → Developer settings → Personal access tokens → Tokens (classic)
- Click "Generate new token (classic)"
- Name:
Jenkins CI - Expiration: 90 days or No expiration
- Select scopes:
- ✅
repo(Full control of repositories) - ✅
workflow(Update GitHub Action workflows)
- ✅
- Click "Generate token"
⚠️ Copy the token immediately (you won't see it again!)
-
Add to Jenkins:
- Go to Jenkins → Manage Jenkins → Credentials → System → Global credentials
- Click "Add Credentials"
- Kind: Username with password
- Username:
eknathdj(your GitHub username) - Password: Paste your GitHub PAT
- ID:
github-creds - Description: GitHub Personal Access Token
- Click "Create"
- Add to Jenkins:
- Same path: Manage Jenkins → Credentials → System → Global credentials
- Click "Add Credentials"
- Kind: Username with password
- Username: Your DockerHub username
- Password: Your DockerHub password or access token
- ID:
dockerhub-creds - Description: DockerHub Registry Credentials
- Click "Create"
-
Create New Pipeline:
- Click "New Item"
- Name:
sample-app-pipeline - Select "Pipeline"
- Click "OK"
-
Configure Pipeline:
-
Description: CI/CD Pipeline for DevOps Sample App
-
Build Triggers:
- ✅ Check "GitHub hook trigger for GITScm polling"
-
Pipeline Definition:
- Select "Pipeline script from SCM"
- SCM: Git
- Repository URL:
https://github.com/eknathdj/devops-argocd-jenkins-app - Credentials: Select
github-creds - Branch:
*/main - Script Path:
Jenkinsfile
-
Additional Behaviours:
- Add "Wipe out repository & force clone" (optional, for clean builds)
-
-
Click "Save"
- Go to your GitHub repository:
https://github.com/eknathdj/devops-sample-app - Click Settings → Webhooks → Add webhook
- Configure:
- Payload URL:
http://<YOUR_JENKINS_IP>:8080/github-webhook/- Replace
<YOUR_JENKINS_IP>with your Jenkins server IP - For local testing with public access, use ngrok:
ngrok http 8080 # Use the ngrok URL: https://xxxx.ngrok.io/github-webhook/
- Replace
- Content type:
application/json - Events: Select "Just the push event"
- Active: ✅ Checked
- Payload URL:
- Click "Add webhook"
Ensure your repository has this structure:
devops-sample-app/
├── .gitignore # Git ignore rules
├── Dockerfile # Docker build instructions
├── Jenkinsfile # CI/CD pipeline definition
├── package.json # Node.js dependencies
├── package-lock.json # Lockfile for dependencies
├── server.js # Main application file
├── healthcheck.js # Health check endpoint
├── prometheus-alerts.yaml # Prometheus alerting rules
├── devops-pipeline-dashboard.json # Grafana dashboard JSON
├── SECURITY.md # Security policy
├── README.md # This file
├── argocd/
│ ├── production-application.yaml # ArgoCD app for production
│ └── staging-application.yaml # ArgoCD app for staging
├── argocd-servicemonitor.yaml # ArgoCD service monitor
├── environments/
│ ├── production/
│ │ ├── deployment.yaml # K8s deployment for production
│ │ ├── service.yaml # K8s service for production
│ │ ├── namespace.yaml # K8s namespace for production
│ │ └── kustomization.yaml # Kustomize config for production
│ └── staging/
│ ├── deployment.yaml # K8s deployment for staging
│ ├── service.yaml # K8s service for staging
│ ├── namespace.yaml # K8s namespace for staging
│ ├── kustomization.yaml # Kustomize config for staging
│ └── servicemonitor.yaml # Service monitor for staging
├── jenkins/
│ ├── docker-compose.yml # Jenkins Docker Compose
│ └── init-scripts/ # Jenkins initialization scripts
├── scripts/
│ ├── install-argocd.sh # ArgoCD installation script
│ ├── install-monitoring.sh # Monitoring stack installation
│ └── test-pipeline.sh # Pipeline testing script
└── jenkins-servicemonitor.yaml # Jenkins service monitor
- Log in to DockerHub
- Click "Create Repository"
- Configure:
- Name:
devops-sample-app - Visibility: Public (or Private if preferred)
- Description: Sample DevOps application
- Name:
- Click "Create"
Your image will be: <username>/devops-sample-app
Edit the DOCKER_IMAGE environment variable in your Jenkinsfile:
environment {
DOCKER_IMAGE = "your-dockerhub-username/devops-sample-app"
// ... rest of config
}Use the provided installation script:
chmod +x scripts/install-argocd.sh
./scripts/install-argocd.shOr manually:
# Create namespace
kubectl create namespace argocd
# Install ArgoCD
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
# Wait for pods to be ready
kubectl wait --for=condition=available --timeout=300s deployment/argocd-server -n argocdOption A: Port Forward (Local Access)
kubectl port-forward svc/argocd-server -n argocd 8081:443Access at: https://localhost:8081
Option B: LoadBalancer (Cloud)
kubectl patch svc argocd-server -n argocd -p '{"spec": {"type": "LoadBalancer"}}'
kubectl get svc argocd-server -n argocd# For ArgoCD 2.0+
kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d && echo
# Username: admin
# Password: (output from above command)# Linux
curl -sSL -o /usr/local/bin/argocd https://github.com/argoproj/argo-cd/releases/latest/download/argocd-linux-amd64
chmod +x /usr/local/bin/argocd
# macOS
brew install argocd
# Login via CLI
argocd login localhost:8081 --username admin --password <password> --insecureUse the pre-configured application manifests:
# For staging
kubectl apply -f argocd/staging-application.yaml
# For production
kubectl apply -f argocd/production-application.yamlOr create manually via UI/CLI as described in the original setup.
Use the provided installation script:
chmod +x scripts/install-monitoring.sh
./scripts/install-monitoring.shOr manually:
# Install Prometheus and Grafana
kubectl create namespace monitoring
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm install prometheus prometheus-community/kube-prometheus-stack \
-n monitoring
# Access Grafana
kubectl port-forward svc/prometheus-grafana -n monitoring 3000:80
# Default: admin / prom-operator- Access Grafana at
http://localhost:3000 - Go to Dashboards → Import
- Upload
devops-pipeline-dashboard.json - Select the Prometheus data source
Apply the service monitors:
kubectl apply -f jenkins-servicemonitor.yaml
kubectl apply -f argocd-servicemonitor.yaml
kubectl apply -f environments/staging/servicemonitor.yaml- Trigger: Push to GitHub → Webhook notifies Jenkins
- Setup: Jenkins configures Git and cleans workspace
- Checkout: Clone repository
- Build: Create Docker image tagged with build number
- Push: Upload image to DockerHub
- Update: Modify Kubernetes manifest with new image tag
- Commit: Push manifest changes back to GitHub
- Deploy: ArgoCD detects changes and syncs to Kubernetes
To manually trigger a build:
# In Jenkins UI: Click "Build Now" on your pipeline
# Or via CLI:
curl -X POST http://localhost:8080/job/sample-app-pipeline/build \
--user admin:<your-jenkins-token># Watch ArgoCD sync status
argocd app get sample-app-staging --refresh
# Watch Kubernetes pods
kubectl get pods -n staging -w
# Check application logs
kubectl logs -f deployment/sample-app -n staging
# Get service endpoint
kubectl get svc -n stagingCause: Git ownership/permission issue
Solution:
# Inside Jenkins container
docker exec -it jenkins bash
git config --global --add safe.directory '*'
exit
docker restart jenkinsCause: GitHub credentials invalid or expired
Solution:
- Generate new GitHub Personal Access Token (PAT)
- Update
github-credsin Jenkins with new PAT - Ensure PAT has
repoandworkflowscopes
Cause: Docker socket not mounted or permission issue
Solution:
# Ensure socket is mounted
docker run -v /var/run/docker.sock:/var/run/docker.sock ...
# Or give Jenkins user permission
docker exec -it jenkins bash
chmod 666 /var/run/docker.sockCause: Manifest changes not detected or sync policy issue
Solution:
# Force refresh
argocd app get sample-app-staging --refresh
# Manual sync
argocd app sync sample-app-staging
# Check diff
argocd app diff sample-app-stagingCause: Image not found in registry or auth issue
Solution:
# Verify image exists in DockerHub
# Check image name in deployment.yaml matches exactly
# For private repos, create imagePullSecret
kubectl create secret docker-registry dockerhub-secret \
--docker-server=https://index.docker.io/v1/ \
--docker-username=<username> \
--docker-password=<password> \
-n staging
# Add to deployment.yaml:
# imagePullSecrets:
# - name: dockerhub-secretCause: File path incorrect or sed command issue
Solution:
# Verify file exists
ls -la environments/staging/deployment.yaml
# Test sed command locally
sed -i 's|image: .*|image: new-image:tag|g' environments/staging/deployment.yaml# Jenkins logs
docker logs jenkins -f
# ArgoCD logs
kubectl logs -f deployment/argocd-server -n argocd
# Application logs
kubectl logs -f deployment/sample-app -n staging
# Describe pod for details
kubectl describe pod <pod-name> -n staging
# Check events
kubectl get events -n staging --sort-by='.lastTimestamp'-
Rotate Credentials Regularly
- GitHub PATs: Rotate every 90 days
- DockerHub tokens: Use access tokens instead of passwords
- Jenkins credentials: Audit quarterly
-
Use Secrets Management
# Store sensitive data in Kubernetes secrets kubectl create secret generic app-secrets \ --from-literal=api-key=xxx \ -n staging -
Enable RBAC
- Restrict Jenkins service account permissions
- Use least-privilege principle in Kubernetes
-
Use Build Caching
# In Dockerfile, order layers by change frequency COPY package.json . RUN npm install COPY . .
-
Parallel Stages
parallel { stage('Test') { ... } stage('Security Scan') { ... } } -
Conditional Deployment
when { branch 'main' expression { currentBuild.result == 'SUCCESS' } }
-
Separate Config from Code
- Keep manifests in
environments/directory - Use different branches for environments (optional)
- Keep manifests in
-
Version Everything
- Always use specific image tags (never
:latest) - Tag format:
v1.0.0-${BUILD_NUMBER}
- Always use specific image tags (never
-
Environment Parity
- Use same base manifests with overlays
- Consider using Kustomize or Helm
Contributions are welcome! Please follow these steps:
- Fork the repository
- Create a feature branch:
git checkout -b feature/amazing-feature - Commit changes:
git commit -m 'Add amazing feature' - Push to branch:
git push origin feature/amazing-feature - Open a Pull Request
# Clone your fork
git clone https://github.com/eknathdj/devops-argocd-jenkins-app.git
# Add upstream remote
git remote add upstream https://github.com/eknathdj/devops-argocd-jenkins-app.git
# Create feature branch
git checkout -b feature/my-feature
# Make changes and test locally
docker build -t test:local .
docker run -p 8080:8080 test:local
# Commit and push
git add .
git commit -m "Description of changes"
git push origin feature/my-featureThis project is licensed under the MIT License - see the LICENSE file for details.
Eknath DJ
- GitHub: @eknathdj
- Repository: devops-sample-app
- Jenkins community for excellent CI/CD tooling
- ArgoCD team for GitOps innovation
- Kubernetes community for container orchestration
- Prometheus and Grafana teams for monitoring excellence
- All contributors to this project
If you encounter issues:
- Check the Troubleshooting section
- Search existing GitHub Issues
- Create a new issue with:
- Clear description of the problem
- Steps to reproduce
- Error logs
- Environment details (OS, versions, etc.)
⭐ If this project helped you, please give it a star!
Last Updated: October 2025