DEV Community

Devanand
Devanand

Posted on

CI/CD Pipeline Best Practices: A Practical Guide for 2025

CI/CD Pipeline Best Practices: A Practical Guide for 2025

Service: SEO-Optimized Blog Post | Price: $15 | Format: dev.to-ready | Category: DevOps

Modern CI/CD pipelines are the backbone of software delivery. A well-architected pipeline catches bugs early, deploys with confidence, and keeps your team shipping fast. A poorly designed one becomes a bottleneck that everyone dreads.

This guide covers battle-tested patterns for building pipelines that scale — from small side projects to production monorepos.


1. Pipeline Architecture Patterns

The Three-Stage Pipeline

Every pipeline should have three clear stages:

# Minimal three-stage pipeline
name: CI/CD
on: [push, pull_request]

jobs:
  verify:     # Lint, typecheck, unit tests
  build:      # Build artifacts, run integration tests
  deploy:     # Deploy to environment (gated on main branch)
Enter fullscreen mode Exit fullscreen mode

Why three stages?

  • Verify fails fast (under 2 minutes)
  • Build validates integration (under 5 minutes)
  • Deploy requires human approval for production

Monorepo Optimization

Monorepos need smart change detection:

# Path-based filtering for monorepos
jobs:
  api-tests:
    if: contains(github.event.head_commit.modified, 'packages/api/')
    runs-on: ubuntu-latest

  ui-tests:
    if: contains(github.event.head_commit.modified, 'packages/ui/')
Enter fullscreen mode Exit fullscreen mode

2. Speed Optimization

Parallel Job Execution

Run independent jobs in parallel instead of sequential stages:

jobs:
  lint:
  typecheck:
  unit-tests:
    # All three run simultaneously
Enter fullscreen mode Exit fullscreen mode

Docker Layer Caching

Speed up Docker builds by 70%:

- name: Set up Docker Buildx
  uses: docker/setup-buildx-action@v3

- name: Cache Docker layers
  uses: actions/cache@v4
  with:
    path: /tmp/.buildx-cache
    key: ${{ runner.os }}-buildx-${{ hashFiles('**/Dockerfile') }}
Enter fullscreen mode Exit fullscreen mode

Dependency Cache

Never re-download unchanged dependencies:

- name: Cache pnpm
  uses: actions/cache@v4
  with:
    path: ~/.pnpm-store
    key: ${{ runner.os }}-pnpm-${{ hashFiles('pnpm-lock.yaml') }}
Enter fullscreen mode Exit fullscreen mode

3. Testing Strategy

Fail Fast, Fail Early

Run the fastest checks first:

  1. TypeScript typecheck (10s)
  2. Unit tests (30s)
  3. Integration tests (2min)
  4. E2E tests (10min)

Test Matrix Against Real Environments

strategy:
  matrix:
    node-version: [18, 20, 22]
    os: [ubuntu-latest, windows-latest]

steps:
  - uses: actions/setup-node@v4
    with:
      node-version: ${{ matrix.node-version }}
Enter fullscreen mode Exit fullscreen mode

4. Deployment Safety

Environment Gates

deploy-staging:
  needs: [lint, typecheck, unit-tests]
  environment: staging

deploy-production:
  needs: [deploy-staging]
  environment: production
  if: github.ref == 'refs/heads/main'
Enter fullscreen mode Exit fullscreen mode

Zero-Downtime Deployments

- name: Deploy with health check
  run: |
    docker compose up -d --wait --wait-timeout 60
    curl --retry 5 --retry-delay 5 http://localhost:3100/api/health
Enter fullscreen mode Exit fullscreen mode

Rollback Automation

- name: Rollback on failure
  if: failure()
  run: |
    docker compose down
    docker compose -f docker-compose.previous.yml up -d
Enter fullscreen mode Exit fullscreen mode

5. Real-World Configurations

Full GitHub Actions CI Pipeline

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

jobs:
  verify:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: 20, cache: pnpm }

      - run: pnpm install --frozen-lockfile
      - run: pnpm typecheck
      - run: pnpm lint
      - run: pnpm test

  build:
    needs: verify
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: pnpm install --frozen-lockfile
      - run: pnpm build

  docker:
    needs: build
    runs-on: ubuntu-latest
    steps:
      - uses: docker/build-push-action@v5
        with:
          cache-from: type=gha
          cache-to: type=gha,mode=max
Enter fullscreen mode Exit fullscreen mode

GitLab CI Pipeline

stages:
  - verify
  - build
  - deploy

verify:test:
  stage: verify
  script:
    - npm ci
    - npm run typecheck
    - npm run test
  cache:
    key: $CI_COMMIT_REF_SLUG
    paths:
      - node_modules/
Enter fullscreen mode Exit fullscreen mode

Key Takeaways

  1. Three-stage architecture — Verify → Build → Deploy
  2. Parallel execution — Independent jobs run simultaneously
  3. Layer caching — Cut Docker build times by 70%
  4. Environment gates — Never deploy untested code to production
  5. Health checks — Always verify the deployment actually works

This post is part of the Production DevOps Patterns series. Follow for more DevOps, CI/CD, and infrastructure best practices.

Publish-ready: Copy this markdown directly to dev.to, Medium, or your blog. Frontmatter included.

Top comments (0)