Back to Blog

CI/CD Pipeline Best Practices for Teams

How to design reliable CI/CD pipelines that catch bugs early, automate deployments, and give your team confidence in every release.

What CI/CD Actually Solves

Before CI/CD, software deployment was a stressful event. Developers worked in isolation for weeks. Code integration happened at the last minute. Testing was manual and rushed. Deployments happened on Friday evenings with the team holding their breath. Rollbacks meant restoring from backups and hoping for the best.

Continuous Integration and Continuous Delivery solve this by making integration, testing, and deployment automatic, frequent, and reproducible. Instead of one terrifying monthly release, you ship small changes multiple times per day with confidence.

Continuous Integration: The Foundation

The Core Practice

Every developer pushes code to the main branch at least once per day. Each push triggers an automated build and test run. If anything breaks, the team fixes it immediately.

This is the non-negotiable rule: the main branch is always in a deployable state.

A Practical CI Pipeline

# .github/workflows/ci.yml
name: CI Pipeline

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  lint:
    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

  test:
    runs-on: ubuntu-latest
    needs: lint
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'npm'
      - run: npm ci
      - run: npm test -- --coverage
      - uses: actions/upload-artifact@v4
        with:
          name: coverage-report
          path: coverage/

  build:
    runs-on: ubuntu-latest
    needs: test
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'npm'
      - run: npm ci
      - run: npm run build

This pipeline runs three stages in sequence: lint, test, build. If linting fails, tests do not run. If tests fail, the build does not happen. Fast feedback, minimal wasted compute.

Pipeline Speed Matters

A CI pipeline that takes 45 minutes discourages frequent commits. Developers batch changes to avoid waiting, which defeats the purpose of continuous integration.

Target pipeline times:

StageTarget Duration
LintUnder 2 minutes
Unit testsUnder 5 minutes
BuildUnder 5 minutes
Integration testsUnder 10 minutes
Total pipelineUnder 15 minutes

Techniques to achieve this:

  • Parallel execution: Run lint, type check, and unit tests simultaneously
  • Dependency caching: Cache node_modules, Docker layers, and build artifacts
  • Selective testing: Only run tests affected by changed files (requires test infrastructure support)
  • Incremental builds: Only rebuild what changed

Continuous Delivery: From Build to Deployment

Environment Progression

Code moves through environments in a defined sequence:

Development → Staging → Production

Each environment serves a purpose:

  • Development: Latest code from main. Used by developers for integration testing.
  • Staging: Mirror of production. Used for final validation, performance testing, and stakeholder review.
  • Production: Live customer-facing environment.

Deployment Strategies

Rolling deployment: Replace instances one at a time. Zero downtime. Easy rollback by continuing with old versions.

Blue-green deployment: Run two identical environments. Route traffic from blue (current) to green (new) after verification. Instant rollback by routing back to blue.

Canary deployment: Route a small percentage of traffic (5% to 10%) to the new version. Monitor error rates and performance. Gradually increase traffic if healthy.

# Canary deployment configuration example
canary:
  steps:
    - setWeight: 5
    - pause: { duration: 5m }
    - analysis:
        templates:
          - templateName: error-rate
        args:
          - name: service-name
            value: web-app
    - setWeight: 25
    - pause: { duration: 10m }
    - setWeight: 50
    - pause: { duration: 10m }
    - setWeight: 100

For most teams, rolling deployments are the right starting point. Move to canary when your monitoring is mature enough to detect problems during the rollout.

Testing in the Pipeline

The Test Pyramid

Structure your tests with more fast tests and fewer slow tests:

        /  E2E Tests  \        Few, slow, expensive
       /  Integration   \      Some, moderate speed
      /   Unit Tests      \    Many, fast, cheap
  • Unit tests: Run on every commit. Cover business logic, utilities, and data transformations.
  • Integration tests: Run on every commit or pull request. Cover API endpoints, database queries, and service interactions.
  • End-to-end tests: Run before deployment to staging and production. Cover critical user journeys.

Quality Gates

Define non-negotiable criteria that must pass before code moves forward:

  • All tests pass (zero failures)
  • Code coverage above threshold (80% minimum)
  • No critical or high security vulnerabilities
  • No linting errors
  • Build produces valid artifacts
  • Docker image size within budget

Automate these gates. Human oversight should be reserved for code review, not for checking whether tests passed.

Security in the Pipeline

Shift Left

Integrate security checks early in the pipeline rather than as a final gate:

security:
  runs-on: ubuntu-latest
  steps:
    - uses: actions/checkout@v4
    - name: Dependency audit
      run: npm audit --audit-level=high
    - name: Secret scanning
      uses: trufflesecurity/trufflehog@main
      with:
        path: ./
    - name: SAST scan
      uses: github/codeql-action/analyze@v3
  • Dependency scanning: Check for known vulnerabilities in third-party packages
  • Secret scanning: Detect accidentally committed API keys, passwords, or tokens
  • Static analysis (SAST): Identify security patterns in your own code
  • Container scanning: Check Docker images for vulnerable base layers

Secrets Management

Never store secrets in pipeline configuration files. Use:

  • Environment-level secrets in your CI/CD platform
  • External secret managers (HashiCorp Vault, AWS Secrets Manager)
  • Dynamic credential generation for short-lived access

Monitoring and Rollback

Post-Deployment Verification

After every deployment, automatically verify:

  • Health check endpoints return 200
  • Error rates remain within baseline
  • Response times remain within acceptable range
  • Key business metrics (order processing, payment success) are stable
post-deploy:
  steps:
    - name: Smoke test
      run: |
        for i in $(seq 1 5); do
          STATUS=$(curl -s -o /dev/null -w "%{http_code}" https://app.example.com/health)
          if [ "$STATUS" != "200" ]; then
            echo "Health check failed with status $STATUS"
            exit 1
          fi
        done
        echo "Smoke test passed"

Automated Rollback

When post-deployment checks fail, roll back automatically:

  1. Detect failure through health checks or metrics
  2. Trigger rollback to the previous known-good version
  3. Alert the team with deployment context
  4. Preserve logs and artifacts for investigation

The faster you can roll back, the less impact a bad deployment has. Aim for rollback completion within 5 minutes.

Pipeline Maintenance

Treat Pipelines as Code

Your CI/CD configuration lives in version control alongside your application code. Apply the same standards:

  • Code review pipeline changes
  • Test pipeline changes in a branch before merging
  • Document non-obvious pipeline decisions
  • Refactor when pipelines become too complex

Monitor Pipeline Health

Track these metrics weekly:

  • Build success rate: Target 95%+ (failures should be from real code issues, not flaky infrastructure)
  • Mean time to fix: How quickly are broken builds repaired?
  • Pipeline duration trend: Is it getting slower over time?
  • Flaky test rate: Tests that sometimes pass and sometimes fail undermine trust

Common Pitfalls

  1. Ignoring broken builds: A broken main branch should be the team’s top priority. If it stays broken for hours, the CI practice has failed.
  2. Skipping tests for speed: Fast pipelines are built through optimization, not by removing tests.
  3. Manual deployment steps: If a human must click a button or run a script, it will eventually be done wrong.
  4. No rollback plan: Hope is not a deployment strategy. Test your rollback procedure regularly.

Starting Your CI/CD Journey

If your team currently deploys manually, start here:

  1. Week 1: Set up a CI pipeline that runs linting and unit tests on every push
  2. Week 2: Add a build step that produces a deployable artifact
  3. Week 3: Automate deployment to a development environment
  4. Week 4: Add integration tests and deploy to a staging environment
  5. Month 2: Implement automated production deployment with health checks

Each step reduces risk and builds team confidence. Within two months, you will have a functional CI/CD pipeline. Within six months, you will wonder how you ever deployed any other way.

Baca dalam Bahasa Indonesia Versi Indonesia