TL;DR – Quick Takeaways
- Tekton + ArgoCD give you a fully declarative, Kubernetes‑native CI/CD stack that scales with your workloads.
- Separate concerns: Tekton runs the build‑test‑containerize steps, while ArgoCD continuously syncs manifests from Git to clusters.
- A healthy pipeline must handle failures — Tekton can abort and roll back, ArgoCD can auto‑revert to the last good commit.
- Secure secrets with external vaults and Kubernetes CSI; never commit credentials.
- Expect extra CPU/Memory overhead, but the payoff is observable speed, auditability, and GitOps‑driven safety.
Before you start, you need:
- A Kubernetes 1.27+ cluster (managed or self‑hosted) with
kubectlv1.27. - Helm 3.11 installed locally.
- Tekton Pipelines v0.46.0 and Tekton Triggers v0.22.0 CRDs applied.
- ArgoCD v2.7.0 installed in the same cluster (or a dedicated argocd namespace).
- A Git repository (GitHub, GitLab, or Bitbucket) that holds your source code and Kustomize/Helm manifests.
- Access to a secrets manager (HashiCorp Vault 1.13+, AWS Secrets Manager, or Azure Key Vault).
Introduction: The Rise of Cloud‑Native CI/CD
A senior developer at a fintech startup once saw a production rollback take 45 minutes because a Jenkins job hung on a flaky Docker layer. The incident cost the team a lost SLA window and a bruised reputation. When they switched to a Kubernetes‑native stack, the same pipeline completed in under ten seconds, and every change was replayable from Git.
The shift from monolithic CI servers to cloud‑native tools isn’t just hype. CNCF’s 2023 survey shows 71 % of Kubernetes users have embraced GitOps, with ArgoCD topping the list at 53 %. Organizations crave pipelines that live inside the cluster they manage, enabling the same scalability, observability, and security policies across both development and production.
Why Tekton + ArgoCD?
Tekton treats each CI step as a lightweight Kubernetes Pod, letting you reuse cluster resources and apply pod‑level policies. ArgoCD, on the other hand, continuously reconciles the desired state stored in Git with the live state on the cluster—exactly the GitOps promise. Coupling the two gives you a declarative, version‑controlled end‑to‑end workflow without lock‑in to a single vendor.
💡 Pro Tip: Keep your Tekton and ArgoCD manifests under separate directories (
ci/andcd/) in the same repo. This separation clarifies ownership and reduces merge conflicts.
Architectural Foundations: How Tekton and ArgoCD Work Together
Below is a high‑level view of the data flow from a developer’s push to a running service.
flowchart LR
subgraph Dev[Developer]
A[Git Push] --> B[Webhook]
end
subgraph CI[Tekton]
B --> C[Trigger Event]
C --> D[Build Task]
D --> E[Test Task]
E --> F[Containerize Task]
F --> G[Push Image to Registry]
G --> H[Update Kustomize base (image tag)]
end
subgraph CD[ArgoCD]
H --> I[Git Commit (manifests)]
I --> J[ArgoCD Application]
J --> K[Sync to Staging]
K --> L[Canary Rollout with Argo Rollouts]
L --> M[Promote to Production]
end
style Dev fill:#f9f,stroke:#333,stroke-width:2px
style CI fill:#bbf,stroke:#333,stroke-width:2px
style CD fill:#bfb,stroke:#333,stroke-width:2px
Tekton components (Tasks, Pipelines, Triggers)
- Task – a reusable step definition (e.g.,
build,test). Each runs in its own container image. - Pipeline – stitches tasks together, passing the output of one as input to the next.
- Trigger – listens for external events (GitHub webhook, schedule) and starts a PipelineRun.
ArgoCD’s GitOps principles
ArgoCD watches a Git repository for changes to Kubernetes manifests. When a diff appears, it applies the new resources, records the operation, and optionally rolls back if health checks fail.
⚠️ Warning: ArgoCD does not manage image builds. Let Tekton handle that; ArgoCD only syncs manifests that reference the built image.
The combined CI/CD flow
- Developer pushes code → webhook fires.
- Tekton Trigger creates a
PipelineRun. - Tasks compile, test, containerize, and push the image.
- The final task updates the Kustomize overlay with the new image tag and commits back to Git.
- ArgoCD detects the commit, syncs the manifest to the staging namespace.
- If health checks pass, a promotion step (Argo Rollouts) moves the new version to production.
The separation lets teams debug CI failures without touching CD, and vice‑versa.
Prerequisites and Environment Setup
Kubernetes cluster requirements
A multi‑node 1.27 cluster with at least 3 CPU and 4 Gi free per node works for a modest POC. Make sure the API server has the admissionregistration.k8s.io/v1 API enabled; Tekton registers several mutating/validating webhooks.
# Verify version
kubectl version --short
# Example output:
# Client Version: v1.27.0
# Server Version: v1.27.3
Installing Tekton Pipelines and Triggers
# Install Tekton Pipelines
kubectl apply -f https://storage.googleapis.com/tekton-releases/pipeline/previous/v0.46.0/release.yaml
# Verify pods are ready
kubectl rollout status deployment tekton-pipelines-controller -n tekton-pipelines
# Install Tekton Triggers
kubectl apply -f https://storage.googleapis.com/tekton-releases/triggers/previous/v0.22.0/release.yaml
💡 Pro Tip: Pin the manifest URLs to a specific version (as shown) to avoid breaking changes during CI runs.
Deploying and configuring ArgoCD
# Install ArgoCD in its own namespace
kubectl create namespace argocd
helm repo add argo https://argoproj.github.io/argo-helm
helm install argocd argo/argo-cd -n argocd --version 5.51.0
Expose the UI via port‑forward for initial configuration:
kubectl port-forward svc/argocd-server -n argocd 8080:443
Log in with the autogenerated admin password:
kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d
Once logged in, create a Project that scopes the CI/CD repos:
# argocd/project.yaml
apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
name: fintech-platform
namespace: argocd
spec:
description: Project for fintech microservices
sourceRepos:
- https://github.com/nileshblog.tech/fintech-platform.git
destinations:
- namespace: staging
server: '*'
- namespace: production
server: '*'
clusterResourceWhitelist:
- group: '*'
kind: '*'
kubectl apply -f argocd/project.yaml
Step‑by‑Step Implementation: Building the Pipeline
1. Creating Tekton Tasks (Build, Test, Containerize)
Below are three core tasks. Each includes error handling and uses specific container images that ship with the required tools.
Build Task (task-build.yaml)
apiVersion: tekton.dev/v1beta1
kind: Task
metadata:
name: build-go
labels:
version: "0.1"
spec:
params:
- name: git-url
description: Repository URL
- name: revision
description: Git revision (branch/tag/sha)
workspaces:
- name: source
steps:
- name: clone
image: alpine/git:2.42.0
script: |
#!/bin/sh
set -euo pipefail
git clone $(params.git-url) $(workspaces.source.path)/src
cd $(workspaces.source.path)/src
git checkout $(params.revision)
- name: compile
image: golang:1.21-alpine3.18
workingDir: $(workspaces.source.path)/src
script: |
#!/bin/sh
set -euo pipefail
go mod tidy || { echo "go mod failed"; exit 1; }
go build -o /workspace/output/app .
- name: output
image: busybox:1.36.1
script: |
#!/bin/sh
cp /workspace/output/app $(workspaces.source.path)/src/app
Test Task (task-test.yaml)
apiVersion: tekton.dev/v1beta1
kind: Task
metadata:
name: test-go
spec:
workspaces:
- name: source
steps:
- name: unit-test
image: golang:1.21-alpine3.18
workingDir: $(workspaces.source.path)/src
script: |
#!/bin/sh
set -euo pipefail
go test ./... -coverprofile=cover.out || { echo "Tests failed"; exit 1; }
Containerize Task (task-containerize.yaml)
apiVersion: tekton.dev/v1beta1
kind: Task
metadata:
name: containerize
spec:
params:
- name: image
description: Full image name (registry/repo:tag)
workspaces:
- name: source
steps:
- name: build-push
image: gcr.io/kaniko-project/executor:latest
env:
- name: DOCKER_CONFIG
value: /tekton/home/.docker
script: |
#!/busybox/sh
set -e
/kaniko/executor \
--dockerfile=$(workspaces.source.path)/src/Dockerfile \
--context=$(workspaces.source.path)/src \
--destination=$(params.image) \
--snapshotMode=redo || { echo "Kaniko failed"; exit 1; }
⚠️ Warning: Kaniko requires write access to the target registry. Attach a
docker-registrysecret to the task’sServiceAccount.
2. Constructing the Tekton Pipeline and Wiring Triggers
Create a pipeline that stitches the three tasks together and passes the built image tag downstream.
apiVersion: tekton.dev/v1beta1
kind: Pipeline
metadata:
name: ci-pipeline
spec:
params:
- name: git-url
- name: revision
- name: image-repo
workspaces:
- name: shared-workspace
tasks:
- name: build
taskRef:
name: build-go
params:
- name: git-url
value: $(params.git-url)
- name: revision
value: $(params.revision)
workspaces:
- name: source
workspace: shared-workspace
- name: test
runAfter:
- build
taskRef:
name: test-go
workspaces:
- name: source
workspace: shared-workspace
- name: containerize
runAfter:
- test
taskRef:
name: containerize
params:
- name: image
value: "$(params.image-repo):$(params.revision)"
workspaces:
- name: source
workspace: shared-workspace
Now wire a TriggerTemplate that creates a PipelineRun whenever a GitHub push occurs.
apiVersion: triggers.tekton.dev/v1beta1
kind: TriggerTemplate
metadata:
name: ci-trigger-template
spec:
params:
- name: git-url
- name: revision
- name: image-repo
resourcetemplates:
- apiVersion: tekton.dev/v1beta1
kind: PipelineRun
metadata:
generateName: ci-run-
spec:
pipelineRef:
name: ci-pipeline
params:
- name: git-url
value: $(tt.params.git-url)
- name: revision
value: $(tt.params.revision)
- name: image-repo
value: $(tt.params.image-repo)
workspaces:
- name: shared-workspace
volumeClaimTemplate:
metadata:
name: ci-ws
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 2Gi
Finally, bind the template to a TriggerBinding that extracts data from the GitHub webhook payload.
apiVersion: triggers.tekton.dev/v1beta1
kind: TriggerBinding
metadata:
name: github-push-binding
spec:
params:
- name: git-url
value: $(body.repository.clone_url)
- name: revision
value: $(body.ref) # e.g., refs/heads/main
- name: image-repo
value: "registry.example.com/nileshblog.tech/app"
Create an EventListener service to expose the webhook endpoint.
apiVersion: triggers.tekton.dev/v1beta1
kind: EventListener
metadata:
name: ci-listener
spec:
serviceAccountName: tekton-triggers-admin
triggers:
- name: github-push
bindings:
- ref: github-push-binding
template:
ref: ci-trigger-template
Expose it via an Ingress or LoadBalancer so GitHub can POST events.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ci-ingress
annotations:
kubernetes.io/ingress.class: nginx
spec:
rules:
- host: ci.nileshblog.tech
http:
paths:
- path: /hook
pathType: Prefix
backend:
service:
name: el-ci-listener
port:
number: 8080
💡 Pro Tip: Enable GitHub secret verification (HMAC) on the listener to avoid spoofed events.
3. Defining ArgoCD Applications for Staging and Production
Create two ArgoCD Application CRs. Both point at the same repo, but each targets a different namespace and folder.
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: fintech-staging
namespace: argocd
spec:
project: fintech-platform
source:
repoURL: https://github.com/nileshblog.tech/fintech-platform.git
targetRevision: HEAD
path: manifests/staging
destination:
server: https://kubernetes.default.svc
namespace: staging
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: fintech-production
namespace: argocd
spec:
project: fintech-platform
source:
repoURL: https://github.com/nileshblog.tech/fintech-platform.git
targetRevision: HEAD
path: manifests/production
destination:
server: https://kubernetes.default.svc
namespace: production
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
When the containerize task pushes a new image, it also updates the kustomization.yaml in the staging overlay:
# manifests/staging/kustomization.yaml
resources:
- ../../base
images:
- name: registry.example.com/nileshblog.tech/app
newTag: "main-$(git rev-parse --short HEAD)"
The Git commit performed by the Tekton task triggers ArgoCD to sync the staging application. After automated health checks (readiness probes, smoke tests), a promotion step runs Argo Rollouts.
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
name: fintech-api
namespace: production
spec:
replicas: 3
strategy:
canary:
steps:
- setWeight: 25
- pause: {duration: 30s}
- setWeight: 50
- pause: {duration: 30s}
- setWeight: 100
selector:
matchLabels:
app: fintech-api
template:
metadata:
labels:
app: fintech-api
spec:
containers:
- name: api
image: registry.example.com/nileshblog.tech/app:{{.Values.imageTag}}
ports:
- containerPort: 8080
ArgoCD watches the Rollout resource and reports progressive rollout status directly in its UI.
Advanced Patterns and Production Considerations
Implementing Canary Deployments with Argo Rollouts
The previous snippet already shows a simple canary. To make it traffic‑aware, install the analytics plugin:
kubectl apply -f https://raw.githubusercontent.com/argoproj/argo-rollouts/v1.6.2/manifests/install.yaml
Annotate the service with rollouts.kubernetes.io/traffic-weight to let the controller adjust load balancer percentages automatically.
Securing the Pipeline (Credentials, Secrets, RBAC)
External Secret Management
Use the External Secrets Operator to sync Vault secrets into Kubernetes Secret objects.
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: docker-registry-cred
spec:
refreshInterval: 1h
secretStoreRef:
name: vault-backend
kind: SecretStore
target:
name: reg-cred
creationPolicy: Owner
data:
- secretKey: .dockerconfigjson
remoteRef:
key: secret/data/docker/registry
property: config
Mount the reg-cred secret into Tekton tasks via serviceAccountName that references it. For ArgoCD, enable the Vault plugin:
apiVersion: v1
kind: ConfigMap
metadata:
name: argocd-cm
namespace: argocd
data:
configManagementPlugins: |
- name: vault-plugin
init:
command: ["sh", "-c"]
args: ["vault kv get -field=data secret/argocd"]
RBAC Isolation
Grant Tekton’s ServiceAccount only the create and get permissions on Pods in the ci namespace. Deny any delete on production resources.
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: tekton-ci-role
namespace: ci
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["create", "get", "list"]
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: tekton-ci-binding
namespace: ci
subjects:
- kind: ServiceAccount
name: tekton-triggers-admin
namespace: tekton-pipelines
roleRef:
kind: Role
name: tekton-ci-role
apiGroup: rbac.authorization.k8s.io
Cost Optimization and Performance Tuning
Running both Tekton and ArgoCD introduces multiple controllers, each with its own pod. To keep cloud spend under control:
- Pod autoscaling – enable Horizontal Pod Autoscaler (HPA) on Tekton controller (
cpu: 500m) and ArgoCD repo‑server (cpu: 300m). - Image caching – configure Kaniko’s
cache=trueflag; this reduces rebuild time for unchanged layers. - Garbage collection – schedule a nightly job that deletes completed
PipelineRunobjects older than 30 days.
💡 Pro Tip: For workloads that rarely change, set
replicas: 1on the ArgoCDapplication-controller. Scale up only during heavy release cycles.
Trade‑offs, Challenges, and Alternatives
Tekton vs. GitHub Actions / Jenkins X
| Aspect | Tekton (K8s‑native) | GitHub Actions (SaaS) | Jenkins X (Hybrid) |
|---|---|---|---|
| Resource overhead | Multiple controllers (~500 mCPU each) | Negligible (hosted) | Moderate (Jenkins master) |
| Flexibility | Unlimited custom containers | Limited to actions marketplace | Good, but opinionated |
| Learning curve | Steep (CRDs, YAML) | Low | Medium |
| Portability | 100 % cluster‑agnostic | Tied to GitHub | Works on K8s but needs plugins |
My take: If your organization already lives in Kubernetes and you need fine‑grained security policies, the extra CPU cost of Tekton pays off in auditability and isolation.
ArgoCD vs. Flux
- ArgoCD shines with a rich UI, health checks, and built‑in support for Helm/Kustomize.
- Flux offers a smaller controller footprint and tighter integration with
gitops-toolkit. - Both use the same GitOps reconciliation loop; the choice often boils down to operational preference.
Common pitfalls and debugging tips
- Missing webhook secret – Verify that GitHub’s secret matches the
EventListenersignature. - PVC quota errors – Tekton’s workspaces use PersistentVolumeClaims; ensure the namespace has sufficient quota.
- Image pull failures – Double‑check that the
docker-registrysecret resides in the same namespace as the task pod. - ArgoCD sync loops – If a manifest references a non‑existent ConfigMap, ArgoCD will repeatedly attempt to apply and mark the app OutOfSync. Use
kubectl difflocally to spot the mismatch.
Common Errors & Fixes
| Symptom | Likely Cause | Fix |
|---|---|---|
Tekton TaskRun pod fails with “permission denied” | ServiceAccount lacks secret read permission | Bind secret-reader Role to the task’s ServiceAccount. |
ArgoCD Application shows “Health: Degraded” | Liveness probe timeout after rollout | Increase initialDelaySeconds or check container log for startup errors. |
Kaniko executor exits with “no such file or directory” | Dockerfile path mismatch | Ensure --dockerfile points to the correct relative path inside the workspace. |
GitHub webhook returns 404 | Ingress host misconfigured | Verify DNS resolves ci.nileshblog.tech to the LoadBalancer IP. |
PipelineRun stalls at “Pending” | No matching PVC for workspace | Create a StorageClass with appropriate provisioner and set volumeClaimTemplate.storageClassName. |
Conclusion
When to choose this stack
- Your workloads already run on Kubernetes and you want full auditability through GitOps.
- You need independent scaling of CI (builds, tests) and CD (deployment sync).
- Your organization values declarative pipelines that survive cluster migrations.
The future of K8s‑native CI/CD
The CNCF community continues to invest in Tekton and Argo projects, adding features like pipelines-as-code, policy enforcement, and progressive delivery. Expect tighter integration with Service Meshes and AI‑assisted test selection, making the combination even more powerful for enterprise‑scale delivery.
⚠️ Warning: Introducing multiple controllers inevitably raises the operational surface. Pair this stack with robust monitoring (Prometheus + Grafana) and a clear ownership model to keep complexity in check.
Call to Action
If you found this guide helpful, drop a comment below, share it with your team, or subscribe to the newsletter on nileshblog.tech for more deep‑dive articles on cloud‑native engineering.
Author Bio:
I’m Nilesh Raut, a Software Development Engineer with 2+ years of experience, specializing in Go, JavaScript, Python, Docker, Kubernetes, Git, Jenkins, microservices, and system design (LLD/HLD), backed by a strong foundation in data structures and algorithms. Alongside my engineering journey, I bring 4+ years of hands‑on experience in SEO, where I’ve worked extensively on content strategy, keyword research, technical SEO, and organic growth, helping products and businesses scale efficiently by aligning solid technology with search‑driven performance.

