CI/CD and GitOps Pipeline with Jenkins, Docker Hub, and ArgoCD

Shivam SoniShivam Soni
5 min read

An example pipeline flow is shown below. The developer pushes code to a GitHub repo, triggering Jenkins to build and push a Docker image and update Kubernetes manifests. ArgoCD then pulls the updated manifests (using GitOps principles) to deploy into the Kubernetes cluster.

graph TD
  A[Developer] --> B[GitHub]
  B --> C[Jenkins]
  C --> D[Docker Hub]
  C --> E[Update Kubernetes Manifests]
  E --> F[GitHub Manifests]
  F --> G[ArgoCD]
  G --> H[Kubernetes Cluster]

ArgoCD is a “declarative, GitOps continuous delivery tool for Kubernetes”. In this flow, Git repositories are the source of truth. When Jenkins updates a manifest in the Git repo, ArgoCD automatically syncs it to the cluster.

1. Clone the Project Repository

Begin by cloning the Git repository containing the Flask app, Dockerfile, Jenkinsfile, and Kustomize manifests. For example:

git clone https://github.com/ShivamSoni1995/Flask-App-ArgoCD.git

This repo should include your Flask source (e.g. app.py), Dockerfile, a Jenkinsfile, and a kustomize/ directory with Kubernetes manifests.

2. Flask App and Dockerfile

Create a simple Flask application (e.g. app/app.py) and a requirements.txt. For example:

from flask import Flask
app = Flask(__name__)

@app.route("/")
def hello():
    return "Hello from Flask!"

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

Include a Dockerfile to containerize it. For instance:

FROM python:3.9-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
CMD ["python", "app.py"]

This builds a Docker image with the Flask app. Push this code to GitHub so Jenkins can build it.

3. Kubernetes Manifests with Kustomize

Define your Deployment and Service YAML under a Kustomize base. These are the deployment and service YAML

# kustomize/base/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: flask-app
  labels:
    app: flask-app
spec:
  replicas: 2
  selector:
    matchLabels:
      app: flask-app
  template:
    metadata:
      labels:
        app: flask-app
    spec:
      containers:
      - name: flask-app
        image: youruser/flask-app:v1       # initial tag
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: flask-app-svc
spec:
  type: LoadBalancer
  selector:
    app: flask-app
  ports:
    - port: 80
      targetPort: 80

Using Kustomize makes it easy to patch image tags. ArgoCD natively supports Kustomize applications.

Kustomization.yaml

# kustomize/base/kustomization.yaml
resources:
- deployment.yaml
- service.yaml

4. Jenkins Pipeline (CI)

Create a Jenkinsfile (declarative pipeline) to build, tag, and push the Docker image, then update the manifest in Git. Key stages:

pipeline {
  agent any

  environment {
    IMAGE = "shivamsoni1995/flask-app-argocd"
    TAG = "v${BUILD_NUMBER}"
    BRANCH = "main"
    GIT_REPO = "https://github.com/ShivamSoni1995/Flask-App-ArgoCD.git"
  }

  stages {
    stage('Checkout') {
      steps {
        git branch: "${BRANCH}", credentialsId: 'github-creds', url: "${GIT_REPO}"
      }
    }

    stage('Build & Push Docker Image') {
      steps {
        withCredentials([usernamePassword(credentialsId: 'dockerhub-creds', usernameVariable: 'DOCKER_USER', passwordVariable: 'DOCKER_PASS')]) {         
          sh "docker build -t ${IMAGE}:${TAG} ."
          sh "echo $DOCKER_PASS | docker login -u $DOCKER_USER --password-stdin"
          sh "docker push ${IMAGE}:${TAG}"
        }
      }
    }

    stage('Update Image in deployment.yml') {
      steps {
        sh "yq e '.spec.template.spec.containers[0].image = \"${IMAGE}:${TAG}\"' -i deployment.yml"
      }
    }

    stage('Commit & Push updated deployment.yml') {
      steps {
        withCredentials([usernamePassword(credentialsId: 'github-creds', usernameVariable: 'GIT_USER', passwordVariable: 'GIT_PASS')]) {
          sh """
            git config user.name "jenkins-bot"
            git config user.email "jenkins-bot@example.com"
            git add deployment.yml
            git commit -m "Update image tag to ${TAG} [ci skip]" || echo 'No changes'
            git push https://${GIT_USER}:${GIT_PASS}@github.com/ShivamSoni1995/Flask-App-ArgoCD.git ${BRANCH}
          """

        }
      }
    }
  }

  post {
    always {
      cleanWs()
    }
  }
}

This pipeline first builds youruser/flask-app:${BUILD_NUMBER} and pushes to Docker Hub. It then clones the GitOps repo, updates the deployment.yaml with the new image tag (using yq), commits, and pushes. Ensure you add your Docker Hub credentials (dockerhub-creds) and any necessary GitHub credentials to Jenkins.

5. Register the GitOps Repository in ArgoCD

Install ArgoCD on your cluster and expose its server (e.g. via kubectl patch svc argocd-server to LoadBalancer). Get the initial admin password:

kubectl -n argocd get secret argocd-initial-admin-secret -o \
  jsonpath="{.data.password}" | base64 -d

Then log in (web UI or CLI) using user admin. In ArgoCD, connect the GitHub repo (the one with Kustomize manifests) so ArgoCD can pull it. For example, via CLI:

argocd login <ARGOCD_SERVER>
argocd repo add https://github.com/youruser/gitops-flask.git \
  --username youruser --password <GitHubToken>

This adds the repo (see example syntax). Alternatively, create a Kubernetes secret of type argocd.argoproj.io/secret-type: repository with username and password for GitHub. ArgoCD will use this to fetch manifests.

6. Create the ArgoCD Application

Define an ArgoCD Application resource (or use the UI) that points to your manifest repo. Example flask-app-argocd.yaml

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: flask-app
  namespace: argocd
spec:
  project: default
  source:
    repoURL: https://github.com/ShivamSoni1995/Flask-App-ArgoCD
    targetRevision: HEAD
    path: .
  destination:
    server: https://kubernetes.default.svc
    namespace: default
  syncPolicy:
    automated:
      prune: true
      selfHeal: true

This tells ArgoCD to deploy the kustomize/base manifests from the repo into the default namespace. (The format follows ArgoCD’s spec.) The automated syncPolicy ensures ArgoCD applies changes immediately.

Apply this manifest with kubectl:

kubectl apply -n argocd -f argo-application.yaml

ArgoCD will then create and sync the app. In the ArgoCD UI you should see the flask-app Application with a green sync status.

7. Run Jenkins and Verify Deployment

Trigger the Jenkins job (e.g. by pushing a change or manually). Jenkins will build the Docker image, push it, and commit the updated manifest. ArgoCD will detect the Git change and automatically sync to the cluster. You can verify by:

kubectl get pods -l app=flask-app
kubectl get svc flask-app-svc

The service’s external IP should expose your Flask app. Access it to confirm the deployment.

By combining Jenkins for CI (building images) and ArgoCD for CD (GitOps deployment), this pipeline automates the full workflow. Jenkins handles the multi-stage build/test cycle (Jenkins pipelines are Groovy scripts defining multi-stage workflows), and ArgoCD continuously reconciles the cluster with the git-stored manifests. This ensures reliable, automated delivery of the Flask application into Kubernetes.

0
Subscribe to my newsletter

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

Written by

Shivam Soni
Shivam Soni

🌟I enjoy sharing my insights and experiences with the community through blog posts and speaking engagements.📝💬 My goal is to empower teams to achieve operational excellence through DevOps best practices and a culture of continuous improvement.💪🌟