DevOps

Tekton + ArgoCD — full Kubernetes CI/CD on minikube (from scratch)

Build the complete flow: git push → Tekton builds + pushes image → ArgoCD auto-deploys to cluster. No shortcuts, real webhooks.

Most CI/CD tutorials skip the real complexity. This is the full production pattern deployed on minikube — Tekton for building, ArgoCD for deploying, real GitHub webhooks tying it together.

Why two tools

Tekton = CI (build, test, push image) ArgoCD = CD (deploy to cluster from Git)

Some teams do both with Jenkins or GitLab CI. In Kubernetes-native world, Tekton + ArgoCD is cleaner because:

  • Both run IN your cluster (no external servers)
  • Both use K8s CRDs (Tasks, Pipelines, Applications)
  • Separation of concerns: Tekton knows nothing about deployment; ArgoCD knows nothing about building

The full flow (what you’re building)

Developer pushes code to Git

GitHub sends webhook to Tekton EventListener

Tekton Pipeline runs:
   1. Clone repo
   2. Run tests
   3. Build container image with Kaniko
   4. Push image to registry (Docker Hub)
   5. Update GitOps repo with new image tag

ArgoCD detects Git change

ArgoCD applies updated manifest → cluster runs new image

Two Git repos:

  • App repo → source code
  • GitOps repo → K8s manifests (Tekton updates this, ArgoCD watches it)

Install both

# Tekton
kubectl apply -f https://storage.googleapis.com/tekton-releases/pipeline/latest/release.yaml
kubectl apply -f https://storage.googleapis.com/tekton-releases/triggers/latest/release.yaml
kubectl apply -f https://storage.googleapis.com/tekton-releases/triggers/latest/interceptors.yaml

# ArgoCD
kubectl create namespace argocd
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml --server-side

The --server-side flag on ArgoCD is critical. Without it you’ll hit “metadata.annotations: Too long” errors.

Set up a Tekton Task

Tasks = reusable steps. Example: clone a repo.

apiVersion: tekton.dev/v1beta1
kind: Task
metadata:
  name: git-clone
spec:
  params:
  - name: url
  - name: revision
  workspaces:
  - name: output
  steps:
  - name: clone
    image: alpine/git
    script: |
      cd $(workspaces.output.path)
      git clone --depth 1 -b $(params.revision) $(params.url) .

Pipeline = chain of Tasks

apiVersion: tekton.dev/v1beta1
kind: Pipeline
metadata:
  name: build-and-deploy
spec:
  params:
  - name: repo-url
  - name: image-name
  workspaces:
  - name: shared-data
  tasks:
  - name: clone
    taskRef: { name: git-clone }
    params:
    - name: url
      value: $(params.repo-url)
    workspaces:
    - name: output
      workspace: shared-data
  - name: build-and-push
    runAfter: [clone]
    taskRef: { name: kaniko }
    params:
    - name: image
      value: $(params.image-name)
    workspaces:
    - name: source
      workspace: shared-data

Trigger the pipeline on git push

EventListener + TriggerBinding + TriggerTemplate:

apiVersion: triggers.tekton.dev/v1beta1
kind: EventListener
metadata:
  name: github-listener
spec:
  serviceAccountName: tekton-triggers-sa
  triggers:
  - name: github-push
    bindings:
    - ref: github-push-binding
    template:
      ref: build-and-deploy-template
    interceptors:
    - ref: { name: github }
      params:
      - name: secretRef
        value:
          secretName: github-webhook-secret
          secretKey: token
      - name: eventTypes
        value: ["push"]

Expose the EventListener with ngrok / cloudflared:

kubectl port-forward -n tekton-pipelines svc/el-github-listener 8080:8080
cloudflared tunnel --url http://localhost:8080

You get a public HTTPS URL. Add it as GitHub webhook. Every push = pipeline runs.

ArgoCD — watch the GitOps repo

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: myapp
  namespace: argocd
spec:
  project: default
  source:
    repoURL: https://github.com/<user>/gitops-repo
    targetRevision: HEAD
    path: apps/myapp
  destination:
    server: https://kubernetes.default.svc
    namespace: default
  syncPolicy:
    automated:
      prune: true
      selfHeal: true

Apply. ArgoCD now polls the repo. Any change → auto-syncs to cluster.

The complete loop

  1. Push code
  2. Tekton builds myapp:v1.2.3 → pushes to Docker Hub
  3. Last Tekton step commits image: myapp:v1.2.3 to GitOps repo
  4. ArgoCD sees the change (within 3 min default poll)
  5. ArgoCD applies new manifest → cluster pulls new image → runs it

Zero manual kubectl. Zero SSH. Zero deploy scripts.

The gotcha nobody mentions

Tekton EventListener needs a public URL for GitHub webhooks. On local minikube, use:

  • cloudflared tunnel (free, no signup) — best
  • ngrok (free tier requires signup)

Or skip webhooks initially — trigger pipelines manually with:

kubectl create -f pipelinerun.yaml

Why this is worth the complexity

  • Zero-downtime deploys — K8s rolling updates + ArgoCD sync = smooth
  • Git as source of truth — cluster state = whatever’s in your GitOps repo
  • Instant rollback — revert the GitOps commit, ArgoCD reverts the cluster
  • Multi-cluster — one ArgoCD instance can deploy to 50 clusters
  • Audit trail — every deploy = git commit = who + when

Common failures

  • EventListener not receiving webhooks — check tunnel is still up, verify GitHub webhook shows “delivered”
  • ArgoCD stuck in “OutOfSync” — auto-sync not enabled, or RBAC blocking
  • Pipeline creates image but doesn’t update GitOps repo — SSH key missing for git push step
  • imagePullSecrets missing — pod can’t pull private image after Tekton pushes it

Skip Tekton if…

You’re on a single team. GitHub Actions + ArgoCD is simpler. Tekton shines when:

  • You want CI in-cluster (no external SaaS)
  • You need custom pipeline logic
  • You’re at scale (hundreds of pipelines per day)

Full setup with all YAMLs

For 50+ production-ready YAML templates covering Kubernetes, Tekton, ArgoCD, Terraform and more, see the DevOps YAML Pack.

Recommended

DevOps YAML Pack

36 production-ready configs — Kubernetes, Docker Compose, GitHub Actions, Terraform, Helm, Ansible. Every file heavily commented. Copy, paste, ship.

Get the pack — ₹499 →
Never miss an article