CodeOath
← All posts
CI/CD & DevOps60 min total · 17 parts

CI/CD Pipelines Explained: From git push to Production

Contents — Part 5 of 17: A Real Pipeline, Stage by Stage
Part 5 of 17 · ~1 min

A Real Pipeline, Stage by Stage

# .github/workflows/deploy.yml
name: Deploy
on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  build-and-test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: 20 }
      - run: npm ci
      - run: npm run lint
      - run: npm run build
      - run: npm test          # pipeline stops here if this fails
      - uses: actions/upload-artifact@v4
        with:
          name: build-output
          path: dist/

  deploy:
    needs: build-and-test       # only runs if the job above succeeded
    if: github.ref == 'refs/heads/main'   # never deploy from a PR, only from main
    runs-on: ubuntu-latest
    steps:
      - uses: actions/download-artifact@v4
        with:
          name: build-output
          path: dist/
      - run: npm run deploy

Each stage is a gate: npm test failing stops the deploy job from ever running, because of needs: build-and-test. This is the entire point of CI/CD — a broken build or a failing test physically cannot reach production, because the pipeline itself refuses to proceed. Note that the on: block runs this workflow on both pushes to main and pull requests targeting it — the tests and build run on every PR (giving reviewers a pass/fail signal before merge), but the if: condition on deploy ensures only an actual push to main (i.e., a merged PR) ever triggers a real deployment, never a pull request from an untrusted branch.