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
- Push code
- Tekton builds
myapp:v1.2.3→ pushes to Docker Hub - Last Tekton step commits
image: myapp:v1.2.3to GitOps repo - ArgoCD sees the change (within 3 min default poll)
- 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
imagePullSecretsmissing — 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.
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 →