CI/CD Pipeline for Kubernetes — Automate Build, Test, and Deploy
Why a CI/CD Pipeline Matters
Manual deployments are error-prone, slow, and inconsistent. A CI/CD pipeline automates the entire software delivery process — from code commit to production deployment. For Kubernetes applications, the pipeline builds Docker images, runs tests, pushes to a registry, and updates the cluster automatically.
Why this matters for your career:
- CI/CD is a core DevOps practice (required for most platform engineering roles)
- Automated pipelines reduce deployment errors by 90%+
- GitOps (ArgoCD) is the modern standard for Kubernetes deployments
- Building CI/CD pipelines is a high-value freelance skill
Pipeline Architecture
Code Push → GitHub → Build Docker Image → Run Tests → Push to Registry → Deploy to K8s
Option 1: GitHub Actions + kubectl
# .github/workflows/deploy.yml
name: Deploy to Kubernetes
on:
push:
branches: [main]
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
build-and-deploy:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=semver,pattern={{version}}
type=sha,prefix=
type=raw,value=latest,enable={{is_default_branch}}
- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Set up kubectl
uses: azure/setup-kubectl@v3
with:
version: 'latest'
- name: Deploy to Kubernetes
run: |
# Update image tag in deployment manifest
sed -i "s|image:.*|image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.meta.outputs.version }}|" k8s/deployment.yaml
kubectl apply -f k8s/
kubectl rollout status deployment/my-app -n my-app
env:
KUBECONFIG: ${{ secrets.KUBECONFIG }}
Option 2: GitOps with ArgoCD
ArgoCD follows the GitOps pattern — the Git repository is the single source of truth for your Kubernetes manifests.
# Application manifest (app-of-apps pattern)
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: my-app
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/myorg/my-app-config
targetRevision: HEAD
path: k8s/overlays/production
destination:
server: https://kubernetes.default.svc
namespace: my-app
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
ArgoCD Workflow
- Developer pushes code → GitHub Actions builds and pushes Docker image
- GitHub Actions updates the image tag in the config repo (e.g.,
k8s/overlays/production/kustomization.yaml) - ArgoCD detects the change in the config repo
- ArgoCD syncs the cluster to match the desired state in the config repo
# k8s/overlays/production/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../../base
patches:
- patch: |-
- op: replace
path: /spec/template/spec/containers/0/image
value: ghcr.io/myorg/my-app:v1.2.3
target:
kind: Deployment
name: my-app
Option 3: Helm + GitHub Actions
# .github/workflows/helm-deploy.yml
name: Helm Deploy
on:
push:
branches: [main]
jobs:
helm-deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build and push Docker image
# ... (same as before)
- name: Deploy with Helm
run: |
helm upgrade --install my-app ./charts/my-app \
--set image.tag=${{ steps.meta.outputs.version }} \
--set replicaCount=3 \
-n my-app \
--wait
env:
KUBECONFIG: ${{ secrets.KUBECONFIG }}
Pipeline Stages Comparison
| Stage | GitHub Actions + kubectl | ArgoCD GitOps | Helm | |-------|------------------------|---------------|------| | Code check | ✅ | ✅ | ✅ | | Build image | ✅ | ✅ | ✅ | | Run tests | ✅ | ✅ | ✅ | | Push image | ✅ | ✅ | ✅ | | Update manifest | Inline sed | Separate config repo | Values file | | Deploy to K8s | kubectl apply | ArgoCD sync | helm upgrade | | Rollback | kubectl rollout undo | ArgoCD revert commit | helm rollback | | Drift detection | ❌ Manual | ✅ Automatic | ❌ Manual | | Complexity | Medium | High (requires ArgoCD) | Medium | | Best for | Small teams, simple apps | Enterprise, compliance | Teams already using Helm |
Testing in the Pipeline
# Add testing stages
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run unit tests
run: npm ci && npm test
- name: Run linting
run: npm run lint
- name: Build Docker image
run: docker build -t my-app:test .
- name: Run container tests
run: |
docker run -d -p 3000:3000 --name test-app my-app:test
sleep 5
curl -f http://localhost:3000/health || exit 1
docker stop test-app
Environment Promotion
# Promote through environments
deploy-dev:
if: github.ref == 'refs/heads/develop'
needs: [test]
steps:
- run: helm upgrade my-app ./charts --set env=dev
deploy-staging:
if: github.ref == 'refs/heads/main'
needs: [test]
steps:
- run: helm upgrade my-app ./charts --set env=staging
deploy-production:
if: startsWith(github.ref, 'refs/tags/v')
needs: [deploy-staging]
steps:
- run: helm upgrade my-app ./charts --set env=production
Best Practices
| Practice | Benefit | |----------|--------| | Immutable tags (use commit SHA, not "latest") | Traceable deployments, easy rollback | | Multi-stage builds | Smaller images, faster builds | | Cache Docker layers | 50-80% faster builds | | Run tests before building image | Fail fast, save time | | Use separate config repo for GitOps | Audit trail, RBAC separation | | Add health check to pipeline | Validate deployment before marking success | | Rollback strategy (undo commit or helm rollback) | Quick recovery from bad deployments | | Secret scanning in pipeline | Prevent credential leaks |
Summary
A CI/CD pipeline automates the path from code to Kubernetes. GitHub Actions builds and tests the code, pushes Docker images, and deploys using kubectl, Helm, or ArgoCD. GitOps with ArgoCD provides automatic drift detection and reconciliation. Choose the approach that matches your team's maturity and compliance needs.
Key takeaways:
- GitHub Actions builds Docker images and runs tests
- kubectl apply is simple but manual for rollback
- Helm packages and versions Kubernetes applications
- ArgoCD provides GitOps — Git is the source of truth
- ArgoCD detects and corrects drift automatically
- Use immutable image tags (SHA, semver), never "latest"
- Promote through environments: dev → staging → production
- Always include health checks in the deployment pipeline
What's Next: DevOps — Docker Compose
The next course covers Docker Compose for multi-service local development — defining services, networks, volumes, and development workflows.