Back to Blog
10 min read

Setting Up CI/CD Pipelines: From Code to Production

CI/CDDevOpsAutomation

Setting Up CI/CD Pipelines: From Code to Production

A well-configured CI/CD pipeline automates testing, building, and deployment, reducing errors and speeding up delivery. Here's how to set up a production-ready CI/CD pipeline for your application.

What is CI/CD?

CI (Continuous Integration): Automatically test and build code when changes are pushed.

CD (Continuous Deployment): Automatically deploy code that passes tests to production.

Benefits:

  • Catch bugs early
  • Deploy faster
  • Reduce manual errors
  • Improve code quality
  • Enable frequent releases

Pipeline Stages

A typical pipeline includes:

1. Lint & Format: Check code style

2. Test: Run unit and integration tests

3. Build: Create production artifacts

4. Deploy: Deploy to staging/production

GitHub Actions Example

Here's a complete GitHub Actions workflow for a Next.js application:

YAML

name: CI/CD Pipeline

on:

push:

branches: [main, develop]

pull_request:

branches: [main]

env:

NODE_VERSION: '20'

jobs:

test:

runs-on: ubuntu-latest

steps:

  • name: Checkout code

uses: actions/checkout@v4

  • name: Setup Node.js

uses: actions/setup-node@v4

with:

node-version: ${{ env.NODE_VERSION }}

cache: 'npm'

  • name: Install dependencies

run: npm ci

  • name: Run linter

run: npm run lint

  • name: Run tests

run: npm test

env:

CI: true

  • name: Build application

run: npm run build

env:

NEXT_PUBLIC_API_URL: ${{ secrets.NEXT_PUBLIC_API_URL }}

deploy-staging:

needs: test

runs-on: ubuntu-latest

if: github.ref == 'refs/heads/develop'

steps:

  • name: Checkout code

uses: actions/checkout@v4

  • name: Setup Node.js

uses: actions/setup-node@v4

with:

node-version: ${{ env.NODE_VERSION }}

cache: 'npm'

  • name: Install dependencies

run: npm ci

  • name: Build application

run: npm run build

env:

NEXT_PUBLIC_API_URL: ${{ secrets.STAGING_API_URL }}

  • name: Deploy to staging

uses: deployment-action

with:

environment: staging

# Add your deployment steps here

deploy-production:

needs: test

runs-on: ubuntu-latest

if: github.ref == 'refs/heads/main'

environment: production

steps:

  • name: Checkout code

uses: actions/checkout@v4

  • name: Setup Node.js

uses: actions/setup-node@v4

with:

node-version: ${{ env.NODE_VERSION }}

cache: 'npm'

  • name: Install dependencies

run: npm ci

  • name: Build application

run: npm run build

env:

NEXT_PUBLIC_API_URL: ${{ secrets.PRODUCTION_API_URL }}

  • name: Deploy to production

uses: deployment-action

with:

environment: production

# Add your deployment steps here

Environment-Specific Deployments

Staging Environment

Deploy from develop branch to staging:

YAML

deploy-staging:

if: github.ref == 'refs/heads/develop'

environment: staging

Production Environment

Deploy from main branch to production:

YAML

deploy-production:

if: github.ref == 'refs/heads/main'

environment: production

Docker-Based Pipeline

For containerized applications:

YAML

jobs:

build-and-push:

runs-on: ubuntu-latest

steps:

  • name: Checkout code

uses: actions/checkout@v4

  • name: Set up Docker Buildx

uses: docker/setup-buildx-action@v3

  • name: Login to Docker Hub

uses: docker/login-action@v3

with:

username: ${{ secrets.DOCKER_USERNAME }}

password: ${{ secrets.DOCKER_PASSWORD }}

  • name: Build and push

uses: docker/build-push-action@v5

with:

context: .

push: true

tags: myapp:latest,myapp:${{ github.sha }}

cache-from: type=registry,ref=myapp:latest

cache-to: type=inline

Testing Strategies

Unit Tests

Run on every commit:

YAML

- name: Run unit tests

run: npm test -- --coverage

Integration Tests

Run on pull requests:

YAML

- name: Run integration tests

run: npm run test:integration

env:

DATABASE_URL: ${{ secrets.TEST_DATABASE_URL }}

E2E Tests

Run before production deployment:

YAML

- name: Run E2E tests

run: npm run test:e2e

env:

BASE_URL: ${{ secrets.STAGING_URL }}

Security Scanning

Add security checks:

YAML

- name: Run security audit

run: npm audit --audit-level=moderate

  • name: Scan for vulnerabilities

uses: snyk/actions/node@master

env:

SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}

Deployment Strategies

Blue-Green Deployment

Deploy new version alongside old, switch traffic:

YAML

- name: Deploy blue environment

run: |

docker-compose -f docker-compose.blue.yml up -d

  • name: Health check

run: |

curl -f http://blue.example.com/health || exit 1

  • name: Switch traffic

run: |

# Switch load balancer to blue

Rolling Deployment

Gradually replace old instances:

YAML

- name: Rolling deployment

run: |

kubectl set image deployment/myapp myapp=myapp:${{ github.sha }}

kubectl rollout status deployment/myapp

Notifications

Notify team on failures:

YAML

- name: Notify on failure

if: failure()

uses: 8398a7/action-slack@v3

with:

status: ${{ job.status }}

text: 'Deployment failed!'

env:

SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }}

Best Practices

1. Use Secrets

Never hardcode secrets:

YAML

env:

DATABASE_URL: ${{ secrets.DATABASE_URL }}

2. Cache Dependencies

Speed up builds with caching:

YAML

- uses: actions/setup-node@v4

with:

cache: 'npm'

3. Parallel Jobs

Run independent jobs in parallel:

YAML

jobs:

test:

# ...

lint:

# ...

build:

needs: [test, lint]

4. Fail Fast

Stop pipeline on first failure:

YAML

jobs:

test:

continue-on-error: false

5. Use Matrix Builds

Test on multiple versions:

YAML

strategy:

matrix:

node-version: [18, 20, 22]

Monitoring Deployments

Track deployment metrics:

  • Deployment frequency
  • Lead time
  • Mean time to recovery (MTTR)
  • Change failure rate

Common Pitfalls

1. Not testing in CI: Tests only run locally

2. Deploying on every commit: No staging environment

3. No rollback plan: Can't revert bad deployments

4. Ignoring failures: Pipeline continues on errors

5. Hardcoded secrets: Security risk

Tools

  • GitHub Actions: CI/CD for GitHub repos
  • GitLab CI: Built-in CI/CD for GitLab
  • Jenkins: Self-hosted CI/CD
  • CircleCI: Cloud-based CI/CD
  • Vercel/Netlify: Automatic deployments for frontend

Conclusion

A well-configured CI/CD pipeline is essential for modern development. Start simple, add complexity as needed, and always prioritize reliability over speed.

Remember: The goal is to deploy with confidence, not to deploy as fast as possible.

Enjoyed this article?

If you found this helpful, let's discuss how we can help with your next project.

Book a call