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.

---

*Need help setting up CI/CD for your project? Contact us to discuss your automation needs.*

Enjoyed this article?

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

Book a call