Skip to main content
Setting Up CI/CD with GitHub Actions
Documentation

Setting Up CI/CD with GitHub Actions

Automate your entire software delivery pipeline from testing to deployment with GitHub Actions, shipping faster with confidence

6 min read View on GitHub

Remember the Friday afternoon deployments? The collective breath-holding as you manually ran commands, the Slack channel erupting with bug reports minutes after release, the weekend spent rolling back changes? Those days can be over. Modern CI/CD pipelines don’t just make deployments less stressful—they fundamentally transform how teams ship software, turning what was once a nerve-wracking event into a boring, repeatable, automated process that runs dozens of times per day.

GitHub Actions brings enterprise-grade automation directly into your repository, eliminating the complexity of external CI/CD platforms while giving you the power to build, test, and deploy with confidence. Every push becomes an opportunity to validate your code. Every merge becomes a potential release. And every deployment becomes predictable, traceable, and reversible.

Why CI/CD Matters

Continuous Integration and Continuous Deployment isn’t just about automation—it’s about shortening the feedback loop between writing code and getting it in front of users. When your pipeline runs automatically on every commit, you catch bugs minutes after they’re introduced, not days later. When deployments are automated, you ship features the moment they’re ready, not when someone remembers to run the deploy script.

The benefits compound quickly. Teams with strong CI/CD practices deploy more frequently, recover from failures faster, and spend less time on manual testing and deployment ceremonies. Instead of dedicating Friday afternoons to releases, developers focus on building features while their pipeline handles the mechanical work of testing, building, and shipping.

Prerequisites

Before building your CI/CD pipeline, you’ll need:

  • A GitHub repository with your project code
  • Basic understanding of YAML syntax
  • A deployment target (cloud platform, server, or hosting service)
  • Tests that can run in an automated environment

Don’t worry if your test coverage is minimal—you can always expand it later. The important thing is having something your pipeline can validate.

Your First Workflow

GitHub Actions workflows live in your repository under .github/workflows/. Each workflow is a YAML file defining when to run and what to do. Let’s start with a simple workflow that runs on every push:

name: CI Pipeline

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

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: '20'
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Run tests
        run: npm test

Save this as .github/workflows/ci.yml and push it to your repository. GitHub will automatically detect the workflow and run it on your next push.

The workflow structure is straightforward: the on section defines triggers, jobs define what to run, and steps define the individual commands. The uses keyword pulls in pre-built actions from the GitHub marketplace, while run executes shell commands.

Testing and Building

A robust CI pipeline doesn’t just run tests—it validates multiple aspects of your code. Expand your workflow to include linting, type checking, and building:

jobs:
  quality:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

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

      - name: Install dependencies
        run: npm ci

      - name: Lint code
        run: npm run lint

      - name: Type check
        run: npm run type-check

      - name: Run tests
        run: npm test -- --coverage

      - name: Build project
        run: npm run build

      - name: Upload build artifacts
        uses: actions/upload-artifact@v4
        with:
          name: build
          path: dist/

The upload-artifact step saves your build output, making it available to other jobs or for download later. This is crucial for deployment—you want to deploy exactly what you tested, not rebuild in production.

For projects with multiple test suites or different environments, you can run jobs in parallel:

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

  test-integration:
    runs-on: ubuntu-latest
    services:
      postgres:
        image: postgres:15
        env:
          POSTGRES_PASSWORD: postgres
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
      - run: npm ci
      - run: npm run test:integration

Jobs run in parallel by default, so your test suite completes faster.

Automated Deployment

The real power of CI/CD comes when you automate deployment. Once tests pass, your code should automatically flow to production. Add a deployment job that depends on your test job:

jobs:
  test:
    # ... previous test job configuration

  deploy:
    needs: test
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

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

      - name: Deploy to production
        env:
          DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}
        run: |
          npm run deploy

The needs keyword ensures deployment only runs after tests pass. The if condition restricts deployment to the main branch. The secrets.DEPLOY_TOKEN pulls sensitive credentials from GitHub’s encrypted secrets storage—never hardcode credentials in your workflow files.

For cloud platforms, specialized actions simplify deployment. Deploying to Vercel looks like:

- name: Deploy to Vercel
  uses: amondnet/vercel-action@v25
  with:
    vercel-token: ${{ secrets.VERCEL_TOKEN }}
    vercel-org-id: ${{ secrets.VERCEL_ORG_ID }}
    vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }}
    vercel-args: '--prod'

AWS, Azure, Google Cloud, and most hosting platforms have official actions that handle authentication and deployment complexity.

Environment Protection

For production deployments, add environment protection rules through GitHub’s repository settings. Navigate to Settings > Environments > New environment, create a “production” environment, and enable required reviewers. Then reference the environment in your workflow:

deploy:
  needs: test
  runs-on: ubuntu-latest
  environment:
    name: production
    url: https://your-app.com
  steps:
    # deployment steps

Now deployments to production require manual approval, preventing accidental releases while keeping the process smooth for intentional ones.

What’s Next

Your CI/CD pipeline is running, but there’s always room to grow. Consider adding:

Matrix builds to test across multiple Node versions or operating systems:

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

Security scanning to catch vulnerabilities before they reach production:

- name: Run security audit
  run: npm audit --audit-level=high

Performance testing to ensure changes don’t degrade speed:

- name: Run Lighthouse CI
  run: npm run lighthouse:ci

Automated releases using semantic-release to publish new versions based on commit messages.

The goal isn’t to automate everything immediately—it’s to build confidence in your pipeline incrementally. Start with tests and basic deployment. Once that’s reliable, add security scanning. Then performance tests. Then automated releases. Each addition makes your pipeline more powerful and your deployments more confident.

The best CI/CD pipeline is the one you trust enough to deploy to production without checking Slack every five minutes. Build that trust one automated step at a time, and soon those Friday afternoon deployments will be a distant memory.

Share:

Learn, Contribute & Share

This guide has a companion repository with working examples and code samples.