CI/CD Best Practices
01 / 03

CI/CD Concepts & Pipelines

CI/CD Concepts & Pipelines

Core Concepts

  • Continuous Integration (CI) — developers merge to main frequently (daily+); each push triggers automated build + test. Finds bugs early, prevents merge hell

  • Continuous Delivery (CD) — every passing build is deployable to production; deployment triggered manually. Artifact is always release-ready

  • Continuous Deployment — every passing build automatically deploys to production. Requires high test confidence and good monitoring/rollback

  • Pipeline stages — typically: Source → Build → Test → Security Scan → Staging Deploy → Smoke Tests → Production Deploy

  • Artifact — build output stored and versioned (Docker image, JAR, zip). Same artifact deployed across all envs (no rebuilds per env)

Deployment Strategies

Blue/Green Deployment:
  Two identical environments (blue = live, green = new)
  Deploy to green → run smoke tests → switch traffic to green
  Blue stays up for instant rollback
  Cost: double infrastructure

Canary Deployment:
  Route small % (1-5%) of traffic to new version
  Monitor error rates, latency → gradually increase %
  Abort if metrics degrade (automatic or manual)
  Tools: Argo Rollouts, AWS CodeDeploy, Nginx/Istio

Rolling Update:
  Replace instances one by one
  Always some of old and new running simultaneously
  Default K8s strategy (maxUnavailable, maxSurge params)
  Cheaper than blue/green; risk of version mismatch

Feature Flags (Feature Toggles):
  Deploy code but enable features selectively
  Decouple deployment from release
  Tools: LaunchDarkly, Unleash, Flagsmith, Vercel Edge Config
  Allows: A/B tests, gradual rollouts, kill switches

Shadow Deployment:
  Mirror production traffic to new service
  Compare responses — new version doesn't serve real users yet
  Good for testing with real data without risk

Pipeline as Code

# GitLab CI — .gitlab-ci.yml
stages: [test, build, deploy]

variables:
  IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA

test:
  stage: test
  image: node:20
  cache:
    key: $CI_COMMIT_REF_SLUG
    paths: [node_modules/]
  script:
    - npm ci
    - npm run lint
    - npm run test -- --coverage
  coverage: '/Lines\s*:\s*(\d+\.?\d*)%/'

build:
  stage: build
  image: docker:24
  services: [docker:24-dind]
  script:
    - docker build -t $IMAGE .
    - docker push $IMAGE
  only: [main]

deploy-staging:
  stage: deploy
  image: bitnami/kubectl
  environment:
    name: staging
    url: https://staging.myapp.com
  script:
    - kubectl set image deployment/app app=$IMAGE -n staging
    - kubectl rollout status deployment/app -n staging
  only: [main]

deploy-production:
  stage: deploy
  environment:
    name: production
    url: https://myapp.com
  script:
    - kubectl set image deployment/app app=$IMAGE -n production
    - kubectl rollout status deployment/app -n production
  when: manual                # require approval
  only: [main]

CI/CD Best Practices

  • Fast feedback — keep CI under 10 minutes. Parallelize test suites, cache dependencies, run unit tests first (fail fast)

  • Immutable artifacts — build once, deploy the same image across staging and production. Never rebuild per environment

  • Secrets management — never hardcode secrets. Use CI secret variables, Vault, AWS Secrets Manager, or OIDC (keyless auth)

  • Rollback strategy — always define how to roll back. K8s: kubectl rollout undo. Keep previous image available. Feature flags for quick disable

  • Branch strategy — trunk-based dev (short-lived branches, merge daily) preferred over GitFlow for CD. Feature flags replace long-lived branches

Keep your own version of these notes — editable, searchable, and organised by your stack.

Start free