DeveloperBootstrap

DevOps

CI/CD Pipeline with GitHub Actions

Build production-grade CI/CD pipelines with GitHub Actions including testing, security scanning, Docker builds, and multi-environment deployment strategies.

What you'll learn

  • Structure a multi-job GitHub Actions workflow
  • Run tests and enforce code quality gates
  • Build and push Docker images to GitHub Container Registry
  • Implement matrix builds across Node.js versions
  • Run security scans with Trivy and CodeQL
  • Deploy to staging and production with environment approvals
  • Cache dependencies for fast build times

Workflow Basics

YAML
1# .github/workflows/ci.yml
2name: CI
3
4on:
5  push:
6    branches: [main, develop]
7  pull_request:
8    branches: [main]
9
10jobs:
11  build:
12    runs-on: ubuntu-24.04
13    strategy:
14      matrix:
15        node-version: [20, 22]
16
17    steps:
18      - uses: actions/checkout@v4
19
20      - uses: pnpm/action-setup@v4
21        with:
22          version: 10
23
24      - uses: actions/setup-node@v4
25        with:
26          node-version: ${{ matrix.node-version }}
27          cache: pnpm
28
29      - run: pnpm install --frozen-lockfile
30      - run: pnpm type-check
31      - run: pnpm lint
32      - run: pnpm test --coverage

Testing & Quality

YAML
1  - name: Run tests with coverage
2    run: pnpm test --coverage --reporter=junit --outputFile=test-results.xml
3
4  - name: Upload test results
5    uses: actions/upload-artifact@v4
6    if: always()
7    with:
8      name: test-results-${{ matrix.node-version }}
9      path: test-results.xml
10
11  - name: Upload coverage to Codecov
12    uses: codecov/codecov-action@v4
13    with:
14      token: ${{ secrets.CODECOV_TOKEN }}
15      fail_ci_if_error: true
16      threshold: 80%

Docker Build & Push

YAML
1  build-image:
2    needs: build
3    runs-on: ubuntu-24.04
4    permissions:
5      contents: read
6      packages: write
7
8    steps:
9      - uses: actions/checkout@v4
10
11      - uses: docker/setup-buildx-action@v3
12
13      - uses: docker/login-action@v3
14        with:
15          registry: ghcr.io
16          username: ${{ github.actor }}
17          password: ${{ secrets.GITHUB_TOKEN }}
18
19      - uses: docker/metadata-action@v5
20        id: meta
21        with:
22          images: ghcr.io/${{ github.repository }}
23          tags: |
24            type=ref,event=branch
25            type=sha,prefix=sha-
26            type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }}
27
28      - uses: docker/build-push-action@v5
29        with:
30          context: .
31          platforms: linux/amd64,linux/arm64
32          push: true
33          tags: ${{ steps.meta.outputs.tags }}
34          cache-from: type=gha
35          cache-to: type=gha,mode=max

Security Scanning

YAML
1  security:
2    runs-on: ubuntu-24.04
3    permissions:
4      security-events: write
5    steps:
6      - uses: actions/checkout@v4
7
8      # Dependency audit
9      - run: pnpm audit --audit-level=high
10
11      # Container image scan
12      - uses: aquasecurity/trivy-action@master
13        with:
14          image-ref: ghcr.io/${{ github.repository }}:sha-${{ github.sha }}
15          severity: HIGH,CRITICAL
16          exit-code: 1
17
18      # Code analysis
19      - uses: github/codeql-action/init@v3
20        with:
21          languages: javascript
22      - uses: github/codeql-action/analyze@v3

Deploy to Production

YAML
1  deploy-staging:
2    needs: [build-image, security]
3    runs-on: ubuntu-24.04
4    environment: staging
5    steps:
6      - uses: appleboy/ssh-action@v1
7        with:
8          host: ${{ secrets.STAGING_HOST }}
9          username: deploy
10          key: ${{ secrets.DEPLOY_KEY }}
11          script: |
12            docker pull ghcr.io/${{ github.repository }}:sha-${{ github.sha }}
13            docker compose -f /opt/app/compose.prod.yml up -d --force-recreate
14
15  deploy-production:
16    needs: deploy-staging
17    runs-on: ubuntu-24.04
18    environment:
19      name: production     # requires manual approval in GitHub Environments
20    steps:
21      - uses: appleboy/ssh-action@v1
22        with:
23          host: ${{ secrets.PROD_HOST }}
24          username: deploy
25          key: ${{ secrets.DEPLOY_KEY }}
26          script: |
27            docker pull ghcr.io/${{ github.repository }}:latest
28            docker compose -f /opt/app/compose.prod.yml up -d --force-recreate

Caching & Optimisation

  • Use cache: pnpm in setup-node — caches ~/.pnpm-store automatically.
  • Use cache-from/cache-to: type=gha in build-push-action for Docker layer caching.
  • Split large workflows into reusable workflows with workflow_call triggers.
  • Use concurrency groups to cancel outdated runs on the same branch.
  • Run independent jobs in parallel — use needs only when there is a true dependency.