ToolBox Hub

GitHub Actions Tutorial: Automate CI/CD for Your Projects in 2026

GitHub Actions Tutorial: Automate CI/CD for Your Projects in 2026

Step-by-step GitHub Actions tutorial. Learn to automate testing, building, and deployment with practical workflow examples for Node.js and Python.

March 17, 20266 min read

What Is CI/CD and Why Does It Matter?

CI/CD stands for Continuous Integration and Continuous Deployment. At its core, it is the practice of automating the repetitive steps that happen every time code changes:

  • Continuous Integration (CI): Automatically run tests and checks when code is pushed or a pull request is opened. This ensures broken code is caught before it merges.
  • Continuous Deployment (CD): Automatically deploy to a staging or production environment when code passes all checks.

Without CI/CD, teams rely on developers remembering to run tests, manually building applications, and manually deploying to servers. This is error-prone and slow. With CI/CD, these steps happen automatically and consistently.

GitHub Actions is GitHub's built-in CI/CD platform. It is deeply integrated with your repository, free for public repositories and generously free-tiered for private ones, and powerful enough to handle workflows from simple linting to complex multi-environment deployments.

Core Concepts

Workflow

A workflow is an automated process defined in a YAML file stored in .github/workflows/. A repository can have multiple workflows, each triggered by different events.

Event (Trigger)

Events define when a workflow runs. Common triggers:

  • push β€” When code is pushed to a branch
  • pull_request β€” When a PR is opened, updated, or synchronized
  • schedule β€” On a cron schedule
  • workflow_dispatch β€” Triggered manually from the GitHub UI

Job

A workflow consists of one or more jobs. Jobs run in parallel by default. Each job runs on a fresh virtual machine (called a runner).

Step

Each job contains a sequence of steps. Steps run sequentially. A step can run a shell command or use a pre-built action.

Action

Actions are reusable units of work published on the GitHub Marketplace. For example, actions/checkout checks out your repository, and actions/setup-node installs Node.js.

Your First Workflow: Node.js Testing

Create .github/workflows/test.yml in your repository:

name: Test

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

      - name: Install dependencies
        run: npm ci

      - name: Run linter
        run: npm run lint

      - name: Run tests
        run: npm test

      - name: Check TypeScript
        run: npm run typecheck

This workflow runs on every push to main or develop, and on every pull request to main. It checks out the code, sets up Node.js 20 (with npm caching for speed), installs dependencies, and runs lint, tests, and type checking sequentially. If any step fails, the workflow fails and GitHub blocks the PR from merging.

Python Workflow: Lint and Test

name: Python CI

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

jobs:
  lint-and-test:
    runs-on: ubuntu-latest

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

      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: '3.12'
          cache: 'pip'

      - name: Install dependencies
        run: |
          pip install --upgrade pip
          pip install -r requirements.txt
          pip install ruff pytest pytest-cov

      - name: Lint with Ruff
        run: ruff check .

      - name: Run tests with coverage
        run: pytest --cov=src --cov-report=xml

      - name: Upload coverage report
        uses: codecov/codecov-action@v4
        with:
          file: ./coverage.xml

Matrix Builds: Test Across Multiple Versions

Use a matrix strategy to run your tests against multiple versions of Node.js or Python simultaneously:

jobs:
  test:
    runs-on: ubuntu-latest

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

    steps:
      - uses: actions/checkout@v4

      - name: Use Node.js ${{ matrix.node-version }}
        uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node-version }}
          cache: 'npm'

      - run: npm ci
      - run: npm test

This creates a separate job for each combination: 3 Node.js versions Γ— 2 OS = 6 parallel jobs. If your library needs to support Node 18, 20, and 22, this catches compatibility issues automatically.

Secrets Management

Never hardcode API keys or passwords in your workflows. Use GitHub Secrets.

Setting up a secret:

  1. Go to your repository β†’ Settings β†’ Secrets and variables β†’ Actions
  2. Click "New repository secret"
  3. Add a name (e.g., API_KEY) and value

Using secrets in a workflow:

steps:
  - name: Deploy to production
    env:
      API_KEY: ${{ secrets.API_KEY }}
      DATABASE_URL: ${{ secrets.DATABASE_URL }}
    run: |
      echo "Deploying with API key..."
      npm run deploy

Secrets are masked in logs β€” even if your code accidentally prints $API_KEY, GitHub will replace it with ***.

Deploy to Cloudflare Pages

Here is a complete workflow that builds a Next.js app and deploys it to Cloudflare Pages:

name: Deploy to Cloudflare Pages

on:
  push:
    branches: [main]

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

  deploy:
    runs-on: ubuntu-latest
    needs: test  # Only deploy if tests pass
    environment: production

    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'

      - name: Install and build
        run: |
          npm ci
          npm run build

      - name: Deploy to Cloudflare Pages
        uses: cloudflare/pages-action@v1
        with:
          apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
          accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
          projectName: my-project
          directory: .next
          gitHubToken: ${{ secrets.GITHUB_TOKEN }}

The needs: test directive ensures the deploy job only runs if the test job succeeds. The environment: production adds a required reviewer approval step for production deployments.

Caching for Faster Workflows

Slow workflows discourage developers from running them. Use caching aggressively:

- name: Cache node_modules
  uses: actions/cache@v4
  with:
    path: ~/.npm
    key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
    restore-keys: |
      ${{ runner.os }}-node-

- name: Cache pip packages
  uses: actions/cache@v4
  with:
    path: ~/.cache/pip
    key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
    restore-keys: |
      ${{ runner.os }}-pip-

The cache key includes a hash of the lock file. When package-lock.json or requirements.txt changes, the cache is invalidated and dependencies are reinstalled. Otherwise, the cache is restored in seconds, dramatically speeding up your workflow.

Conditional Steps and Manual Approvals

Control when steps run with conditions:

- name: Deploy to staging
  if: github.ref == 'refs/heads/develop'
  run: npm run deploy:staging

- name: Deploy to production
  if: github.ref == 'refs/heads/main' && github.event_name == 'push'
  run: npm run deploy:production

- name: Comment on PR
  if: github.event_name == 'pull_request'
  uses: actions/github-script@v7
  with:
    script: |
      github.rest.issues.createComment({
        issue_number: context.issue.number,
        owner: context.repo.owner,
        repo: context.repo.repo,
        body: 'βœ… All checks passed! Ready for review.'
      })

Tips for Production-Quality Workflows

  1. Pin action versions β€” Use actions/checkout@v4 not actions/checkout@main to avoid unexpected breaking changes
  2. Keep workflows fast β€” Target under 5 minutes for CI. Slow pipelines get disabled by frustrated developers
  3. Use path filters β€” Only trigger workflows when relevant files change
  4. Set timeout limits β€” Add timeout-minutes: 10 to jobs to prevent runaway builds from consuming all your minutes
  5. Review workflow permissions β€” Use permissions: read-all or minimal necessary permissions for security

GitHub Actions is one of the most powerful tools available to modern developers. A well-designed workflow catches bugs before they reach production, ensures consistent builds across all environments, and removes the manual toil of deployment. Start with a simple test workflow today, and incrementally add deployment automation as your project matures.

Related Posts