← Back to Articles

Setting Up CI/CD Pipelines for Web Applications

Code

CI/CD pipelines have transformed how I develop and deploy applications. Before implementing them, deployments were stressful and error-prone. Now, with automated pipelines, I can deploy confidently multiple times a day. Setting up CI/CD properly takes some work, but the benefits are enormous.

What is CI/CD?

CI/CD stands for Continuous Integration and Continuous Deployment/Delivery:

  • Continuous Integration (CI): Automatically build and test code changes
  • Continuous Deployment/Delivery (CD): Automatically deploy tested code to production

Why CI/CD matters

I've seen teams struggle with manual deployments. CI/CD eliminates:

  • Human error in deployments
  • Forgotten steps in release processes
  • Inconsistent environments
  • Slow feedback loops
  • Fear of deploying

GitHub Actions basics

GitHub Actions is my go-to for CI/CD. It's built into GitHub and works seamlessly with repositories.

Basic workflow structure:

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

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

jobs:
  test:
    runs-on: ubuntu-latest
    
    steps:
    - uses: actions/checkout@v3
    
    - name: Setup Node.js
      uses: actions/setup-node@v3
      with:
        node-version: '18'
        cache: 'npm'
    
    - name: Install dependencies
      run: npm ci
    
    - name: Run tests
      run: npm test
    
    - name: Run linting
      run: npm run lint

Testing strategies

Comprehensive testing is crucial for CI/CD success.

Unit tests:

- name: Run unit tests
  run: npm test -- --coverage
  env:
    CI: true

Integration tests:

- name: Run integration tests
  run: npm run test:integration
  env:
    DATABASE_URL: ${{ secrets.TEST_DATABASE_URL }}

End-to-end tests:

- name: Run E2E tests
  run: npm run test:e2e
  env:
    BASE_URL: http://localhost:3000

Building and optimization

Automated building ensures consistency.

Next.js build:

- name: Build application
  run: npm run build
  
- name: Check bundle size
  run: npx size-limit

Docker builds:

- name: Build Docker image
  run: docker build -t myapp:${{ github.sha }} .
  
- name: Push to registry
  run: docker push myregistry.com/myapp:${{ github.sha }}

Security scanning

Security should be part of every pipeline.

Dependency scanning:

- name: Run security audit
  run: npm audit --audit-level=moderate
  
- name: Dependency check
  uses: dependency-check/Dependency-Check_Action@main

Code quality checks:

- name: CodeQL Analysis
  uses: github/codeql-action/init@v2
  with:
    languages: javascript

Deployment strategies

Different deployment approaches for different scenarios.

Vercel deployment:

- name: Deploy to Vercel
  run: npx vercel --prod --yes
  env:
    VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}

AWS deployment:

- name: Deploy to AWS
  run: |
    aws s3 sync ./dist s3://my-bucket --delete
    aws cloudfront create-invalidation --distribution-id ${{ secrets.CF_DISTRIBUTION_ID }} --paths "/*"
  env:
    AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
    AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}

Environment management

Different environments for different stages.

Environment-specific secrets:

- name: Deploy to staging
  run: npm run deploy:staging
  env:
    API_URL: ${{ secrets.STAGING_API_URL }}
    DATABASE_URL: ${{ secrets.STAGING_DATABASE_URL }}
  if: github.ref == 'refs/heads/develop'

Manual approvals for production:

production:
  needs: [test, build]
  runs-on: ubuntu-latest
  environment: production
  if: github.ref == 'refs/heads/main'
  
  steps:
  - name: Deploy to production
    run: npm run deploy:prod

Branch protection and PR requirements

Protect your main branches and require quality checks.

Branch protection rules:

# GitHub repository settings
# Require PR reviews
# Require status checks to pass
# Require branches to be up to date
# Include administrators

Pull request checks:

name: PR Checks

on:
  pull_request:
    branches: [ main ]

jobs:
  pr-checks:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v3
    
    - name: Check PR size
      uses: codacy/git-version@2.7.1
      with:
        release-branch: main
    
    - name: PR labels check
      uses: mheap/github-action-required-labels@v2.1.0
      with:
        mode: exactly
        count: 1
        labels: "bug, enhancement, feature"

Artifact management

Store and version build artifacts.

Upload artifacts:

- name: Upload build artifacts
  uses: actions/upload-artifact@v3
  with:
    name: build-files
    path: dist/
    retention-days: 30

Download for deployment:

- name: Download build artifacts
  uses: actions/download-artifact@v3
  with:
    name: build-files
    path: dist/

Caching for performance

Speed up builds with caching.

Node modules caching:

- name: Setup Node.js
  uses: actions/setup-node@v3
  with:
    node-version: '18'
    cache: 'npm'

Docker layer caching:

- name: Docker build with cache
  uses: docker/build-push-action@v4
  with:
    push: false
    tags: myapp:cache
    cache-from: type=gha
    cache-to: type=gha,mode=max

Monitoring and alerting

Keep track of your pipelines.

Pipeline monitoring:

- name: Notify on failure
  if: failure()
  run: |
    curl -X POST -H 'Content-type: application/json' \
    --data '{"text":"Pipeline failed: ${{ github.repository }}"}' \
    ${{ secrets.SLACK_WEBHOOK_URL }}

Performance tracking:

- name: Track build time
  run: |
    echo "Build completed in ${{ steps.build.outputs.duration }} seconds"

Rollback strategies

Be prepared to rollback when things go wrong.

Tagged releases:

- name: Create release
  run: |
    git tag v${{ github.run_number }}
    git push origin v${{ github.run_number }}

Automated rollbacks:

- name: Rollback on failure
  if: failure()
  run: |
    # Deploy previous version
    git checkout v${{ github.run_number }}-1
    npm run deploy

Multi-environment setups

Manage multiple deployment environments.

Development → Staging → Production:

name: Deploy

on:
  push:
    branches: [ main, develop ]

jobs:
  deploy-dev:
    if: github.ref == 'refs/heads/develop'
    runs-on: ubuntu-latest
    steps:
    - name: Deploy to development
      run: echo "Deploying to dev"

  deploy-staging:
    needs: deploy-dev
    runs-on: ubuntu-latest
    steps:
    - name: Deploy to staging
      run: echo "Deploying to staging"

  deploy-prod:
    needs: deploy-staging
    if: github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    steps:
    - name: Deploy to production
      run: echo "Deploying to production"

Common pitfalls

Avoid these common CI/CD mistakes:

  1. Too many manual steps: Automate everything possible
  2. No testing in CI: Always test before deploying
  3. Ignoring security: Scan for vulnerabilities regularly
  4. No rollback plan: Always have a way to rollback
  5. Monolithic pipelines: Break complex pipelines into smaller jobs

Scaling CI/CD

As your team grows, consider these strategies:

Matrix builds: Test across multiple Node.js versions Parallel jobs: Run tests in parallel Self-hosted runners: For better performance Pipeline as code: Store pipeline configuration in version control

Measuring CI/CD success

Track these metrics:

  • Deployment frequency: How often you deploy
  • Lead time: Time from commit to production
  • Failure rate: Percentage of failed deployments
  • Recovery time: How quickly you recover from failures

My CI/CD journey

When I first implemented CI/CD, it felt overwhelming. But starting small and iterating has been key. Begin with basic testing and building, then add deployment, then monitoring and security.

CI/CD has transformed how I work. I can ship code confidently, knowing that automated checks will catch issues before they reach users. The initial setup takes time, but the long-term benefits are worth it.

If you're not using CI/CD yet, start today. Even a simple pipeline with testing and linting will improve your development process. As you grow, add more sophisticated checks and automated deployments.

About the author

Rafael De Paz

Full Stack Developer

Passionate full-stack developer specializing in building high-quality web applications and responsive sites. Expert in robust data handling, leveraging modern frameworks, cloud technologies, and AI tools to deliver scalable, high-performance solutions that drive user engagement and business growth. I harness AI technologies to accelerate development, testing, and debugging workflows.

Tags:

Share: