AltosynAltosyn
Back to Blog
DevOpsJune 3, 2026·12 min read

How to Build a GitHub Actions CI/CD Pipeline (Complete Guide 2026)

A step-by-step guide to building a production-ready GitHub Actions CI/CD pipeline — from first workflow to multi-environment deployments with secrets, caching, and rollback.

Why GitHub Actions in 2026?

GitHub Actions has become the default CI/CD platform for teams on GitHub. It's deeply integrated with pull requests, supports thousands of community actions, and the pricing model (free for public repos, generous for private) makes it accessible to teams of any size.

But most teams only scratch the surface. They copy a basic workflow from the docs, get tests running, and call it done. This guide goes further — covering caching, environments, secrets management, matrix builds, and deployment strategies that actually work in production.


Prerequisites

  • A GitHub repository (public or private)
  • Basic understanding of YAML
  • A deployment target (we'll use AWS in examples, but the concepts apply anywhere)

Part 1: Your First GitHub Actions Workflow

GitHub Actions workflows live in .github/workflows/. Create that directory and add your first workflow file.

Basic CI Workflow

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

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

jobs:
  test:
    runs-on: ubuntu-latest

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

      - name: Set up Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'

      - name: Install dependencies
        run: npm ci

      - name: Run tests
        run: npm test

      - name: Run linting
        run: npm run lint

This runs on every push to main/develop and every pull request. The npm ci command (not npm install) ensures a clean, reproducible install every time.


Part 2: Caching — The Biggest Pipeline Speed Win

Without caching, every run downloads all your dependencies from scratch. With caching, subsequent runs take seconds instead of minutes.

- name: Cache Node modules
  uses: actions/cache@v4
  with:
    path: ~/.npm
    key: runner-node-packagelockhash
    restore-keys: |
      runner-node-

- name: Install dependencies
  run: npm ci

The cache key includes a hash of your package-lock.json. When dependencies change, a new cache is created. When they don't, the cached node_modules are restored in seconds.

Real impact: One client went from 8-minute installs to 45 seconds by adding this single block.

Docker Layer Caching

If you build Docker images in your pipeline, use type=gha cache to store Docker layer cache in GitHub's cache storage — free and automatic.

- name: Set up Docker Buildx
  uses: docker/setup-buildx-action@v3

- name: Build and push
  uses: docker/build-push-action@v5
  with:
    context: .
    push: true
    cache-from: type=gha
    cache-to: type=gha,mode=max

Part 3: Secrets and Environment Variables

Never hardcode credentials. GitHub Actions has a built-in secrets store.

Adding Secrets

Go to your repo → SettingsSecrets and variablesActionsNew repository secret.

Add secrets like AWSACCESSKEYID, AWSSECRETACCESSKEY, and DATABASE_URL.

Using Secrets in Workflows

- name: Deploy to AWS
  env:
    AWS_ACCESS_KEY_ID: secrets-AWS_ACCESS_KEY_ID
    AWS_SECRET_ACCESS_KEY: secrets-AWS_SECRET_ACCESS_KEY
    AWS_DEFAULT_REGION: ap-southeast-1
  run: |
    aws s3 sync ./dist s3://my-bucket

Secrets are masked in logs — if a secret value appears in output, GitHub replaces it with ***.


Part 4: Multi-Environment Deployments

Production-ready pipelines deploy to staging first, then production — with manual approval gates.

Setting Up Environments

Go to SettingsEnvironmentsNew environment. Create staging (automatic deployments) and production (requires manual approval). Add environment-specific secrets to each.

Complete CI/CD Pipeline

name: Deploy

on:
  push:
    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
      - run: npm run build

  deploy-staging:
    needs: test
    runs-on: ubuntu-latest
    environment: staging
    steps:
      - uses: actions/checkout@v4
      - name: Deploy to staging
        run: echo "Deploying to staging..."

  deploy-production:
    needs: deploy-staging
    runs-on: ubuntu-latest
    environment: production
    steps:
      - uses: actions/checkout@v4
      - name: Deploy to production
        run: echo "Deploying to production..."

The environment: production line makes GitHub pause and wait for a required reviewer to approve before the production deploy runs.


Part 5: Matrix Builds — Test Across Multiple Versions

Test your code against multiple Node.js versions or operating systems simultaneously:

jobs:
  test:
    runs-on: matrix-os
    strategy:
      matrix:
        os: [ubuntu-latest, windows-latest]
        node: ['18', '20', '22']

    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: matrix-node
      - run: npm ci
      - run: npm test

This runs 6 parallel jobs (2 OS × 3 Node versions) and shows a matrix view in the GitHub UI.


Part 6: Rollback Strategy

Every production deployment should have a rollback path. The simplest approach is tagging each release:

- name: Tag the release
  run: |
    git tag -a "release-runnumber" -m "Release"
    git push origin "release-runnumber"

To rollback: trigger a new workflow run pointing at the previous tag.

Slack Notifications on Failure

- name: Notify Slack on failure
  if: failure()
  uses: slackapi/slack-github-action@v1.26.0
  with:
    payload: |
      { "text": "Deploy failed on branch by actor" }
  env:
    SLACK_WEBHOOK_URL: secrets-SLACK_WEBHOOK_URL

Common GitHub Actions Mistakes

Not pinning action versions: Use @v4 not @latest. @latest can break your pipeline when the action author releases breaking changes.

No approval gates on production: Always require a human approval before production deployments.

Not using npm ci: npm install is non-deterministic in CI. Always use npm ci.

Ignoring pipeline time: Pipelines over 15 minutes slow down your team. Profile your workflow and add caching everywhere you can.


Need Help Building Your CI/CD Pipeline?

We build and optimise CI/CD pipelines for engineering teams across GitHub Actions, GitLab CI, and Jenkins. Most clients see 50–80% reduction in pipeline time after our engagement.

Talk to a DevOps engineer →

Need hands-on help?

We're a specialist DevOps & Atlassian consulting firm. Book a free call to talk through your specific situation.

Get a Free Consultation