Tutorial

Setting Up Your First CI/CD Pipeline

Tutorial · DevOps

Cover: Setting Up Your First CI/CD Pipeline

Continuous Integration means every change gets tested automatically; Continuous Deployment means a passing change ships automatically too. Together, they remove "did anyone actually run the tests before merging?" as a question that needs a human.

The workflow file

# .github/workflows/ci.yml
name: CI
on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

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

on defines what triggers the workflow — here, any push to main and any PR targeting it. npm ci (not npm install) installs exact versions from the lockfile, which is what you want for reproducible builds.

Adding a deploy step

  deploy:
    needs: test
    if: github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm ci && npm run build
      - name: Deploy
        run: ./scripts/deploy.sh
        env:
          DEPLOY_KEY: ${{ secrets.DEPLOY_KEY }}

needs: test means this job only runs if the test job succeeded — a broken build never reaches the deploy step. The if condition further restricts deploys to pushes on main, so a PR from a branch never triggers one.

Secrets never go in the YAML file. ${{ secrets.DEPLOY_KEY }} pulls from GitHub's encrypted secrets store, configured in the repo settings — never commit an API key or deploy credential directly into a workflow file, even a private repo's.

Reading a failed run

The Actions tab shows each job's steps with individual pass/fail status and full logs per step. A failing test job blocks the PR merge button if you've set branch protection to require it — which is the actual enforcement mechanism, not just a visual warning.

Growing the pipeline

From here, common additions are a linting step, a matrix build testing multiple Node versions, caching node_modules between runs to speed things up, and a staging deploy that runs on every PR before the production deploy on merge to main.

Comments