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

  1. Developer pushes code → GitHub Actions builds and pushes Docker image
  2. GitHub Actions updates the image tag in the config repo (e.g., k8s/overlays/production/kustomization.yaml)
  3. ArgoCD detects the change in the config repo
  4. 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.

Unlock Full Tutorial

This chapter is paid content. Join the project to unlock over 5000 words of deep analysis, including 10+ god-tier Prompts and real Source Code examples!