CI/CD Pipelines
Continuous Integration and Continuous Deployment: pipeline stages, automated testing, artifact management, deployment strategies, and GitOps.
The CI/CD Philosophy
Continuous Integration (CI) is the practice of merging developer changes into a shared branch frequently — ideally multiple times a day — with automated testing on every merge. Continuous Delivery (CD) extends this: every change that passes CI is automatically packaged and ready to release to production with minimal manual steps. Continuous Deployment goes further by automating the production release itself. The goal is to eliminate long-lived feature branches, reduce merge conflicts, and shorten feedback cycles from idea to production.
Pipeline Stages
Fail fast: put the cheapest checks first. Unit tests run in seconds; integration tests take minutes; E2E tests take the longest. A failed lint check should abort the pipeline before wasting time on Docker builds.
GitHub Actions Example
name: CI/CD Pipeline
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
- run: npm ci
- run: npm run lint
- run: npm test -- --coverage
build-and-push:
needs: test
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4
- uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: docker/build-push-action@v5
with:
push: true
tags: ghcr.io/myorg/api:${{ github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max
deploy-staging:
needs: build-and-push
runs-on: ubuntu-latest
environment: staging
steps:
- run: |
kubectl set image deployment/api \
api=ghcr.io/myorg/api:${{ github.sha }} \
--namespace=stagingDeployment Strategies Compared
| Strategy | How It Works | Downtime | Rollback Speed | Risk |
|---|---|---|---|---|
| Recreate | Stop old, start new | Yes | Slow (re-deploy old) | High |
| Rolling | Replace pods incrementally | None | Slow | Medium |
| Blue-Green | Run two envs, switch traffic | None | Instant (flip LB) | Low |
| Canary | Route % of traffic to new version | None | Fast (route 0%) | Very Low |
| Shadow | Mirror traffic to new, don't serve response | None | N/A (testing only) | Minimal |
GitOps with Argo CD
GitOps treats your Git repository as the single source of truth for the desired state of your infrastructure and application configuration. Tools like Argo CD and Flux run in your cluster and continuously reconcile the live state against what's in Git. Benefits: every change is audited (Git history), rollback is `git revert`, and there are no imperative `kubectl apply` scripts scattered across CI jobs.
Separate application and config repositories
Keep your application source code (triggers CI builds) in one repo and your Kubernetes manifests/Helm values (triggers Argo CD sync) in a separate config repo. CI pushes updated image tags to the config repo; Argo CD detects the change and syncs the cluster. This gives a clean separation of concerns and prevents circular triggers.
Artifact Management
Tag Docker images with the Git commit SHA (not `latest`) so every deployment is traceable to an exact code version. Store build artifacts in a dedicated registry (ECR, GCR, Nexus). Use semantic versioning tags (`v1.2.3`) for releases and SHA tags for every commit — your CD system promotes a SHA tag to a semantic tag upon release.
Interview Tip
A common interview question: 'How do you roll back a bad deployment in production?' Walk through: (1) detect via monitors/alerts, (2) in blue-green, flip the load balancer back to the blue environment instantly, (3) in canary, reduce the new-version traffic weight to 0%, (4) in GitOps, `git revert` the commit and let Argo CD reconcile. Mention that rollback is only safe if the database migration is backward-compatible.