Skip to content
CI/CD Best Practices

GitHub Actions

Caching

name: Caching with npm
on: push
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3

      - name: Cache node modules
        id: cache-npm
        uses: actions/cache@v3
        env:
          cache-name: cache-node-modules
        with:
          # npm cache files are stored in `~/.npm` on Linux/macOS
          path: ~/.npm
          key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
          restore-keys: |
            ${{ runner.os }}-build-${{ env.cache-name }}-
            ${{ runner.os }}-build-
            ${{ runner.os }}-

      - if: ${{ steps.cache-npm.outputs.cache-hit != 'true' }}
        name: List the state of node modules
        continue-on-error: true
        run: npm list

      - name: Install dependencies
        run: npm install

      - name: Build
        run: npm run build

      - name: Test
        run: npm test

Debugging Pipelines

Debugging Tools for GitHub Actions

GitHub Actions provides several built-in debugging capabilities to help investigate failures quickly.

1. Enable Debug Logging

When to use: You need more verbose output to understand what’s happening between steps, or want to see hidden commands and environment setup.

How to enable:

Add these secrets to your repository (Settings → Secrets and variables → Actions):

  • ACTIONS_STEP_DEBUG = true - Shows detailed step-by-step execution logs
  • ACTIONS_RUNNER_DEBUG = true - Shows runner diagnostic information

What you get:

  • Detailed logs showing environment variable setup
  • Hidden commands executed by actions
  • Step-by-step execution trace
  • Runner diagnostic information

Example output difference:

Without debug logging:

Run actions/checkout@v4

With debug logging:

##[debug]Evaluating condition for step: 'Checkout code'
##[debug]Evaluating: success()
##[debug]Evaluating success:
##[debug]=> true
##[debug]Result: true
##[debug]Starting: Checkout code
Run actions/checkout@v4
##[debug]Getting Git version info
##[debug]Working directory is '/home/runner/work/repo'
##[debug]Running command: git --version

2. Re-run Jobs with SSH Access

When to use: You need to inspect the CI environment interactively, debug environment-specific issues, or investigate complex failures hands-on.

How to use:

Add the tmate action to your workflow (triggers on failure):

- name: Setup tmate session
  uses: mxschmitt/action-tmate@v3
  if: failure()
  timeout-minutes: 30

Or add it temporarily to debug a specific step:

- name: Debug with tmate
  uses: mxschmitt/action-tmate@v3
  if: always()  # Run even if previous steps succeed

What you get:

  • SSH access to the runner environment
  • Connection string displayed in logs
  • Ability to run commands interactively
  • Inspect files, environment variables, installed tools

Usage tips:

  • Remove or comment out before merging to production
  • Use timeout-minutes to prevent hanging
  • Set repository secret ACTIONS_STEP_DEBUG to see connection info sooner

3. Download and Inspect Artifacts

When to use: You need to examine build outputs, test reports, logs, or coverage data after the run completes.

How to access:

  1. Go to Actions tab in your repository
  2. Click on the workflow run
  3. Scroll to bottom to see Artifacts section
  4. Click to download zip file

Upload debugging artifacts:

- name: Upload test results
  uses: actions/upload-artifact@v4
  if: always()  # Upload even if tests fail
  with:
    name: test-results
    path: |
      test-results/
      coverage/
      **/*.log
    retention-days: 7

Common artifacts to upload for debugging:

  • Test results and reports (test-results/, junit.xml)
  • Code coverage reports (coverage/, htmlcov/)
  • Build logs (*.log, build.log)
  • Screenshots from E2E tests (screenshots/, cypress/screenshots/)
  • Application bundles or builds (dist/, build/)

Tips:

  • Use if: always() to upload even when steps fail
  • Set reasonable retention-days to save storage costs
  • Use descriptive artifact names for multiple uploads
  • Combine related files in single artifact to reduce clutter

Local Validation

Local Validation with act

act runs GitHub Actions workflows locally using Docker, giving you instant feedback without pushing to GitHub.

Installation

brew install act

Requirements: Docker must be installed and running.

Essential Commands

Run workflows:

act                    # Run all push event workflows
act pull_request       # Simulate PR event
act -j job-name        # Run specific job
act -l                 # List all jobs

Working with secrets:

# Inline
act -s API_KEY=value -s TOKEN=abc123

# From file (recommended)
echo "API_KEY=value" >> .secrets
echo ".secrets" >> .gitignore
act

Debugging:

act -n                 # Dry run (show what would execute)
act --verbose          # Verbose output
act -j build --rm=false  # Keep container for inspection

Common Issues

“Error: Cannot connect to Docker daemon”

  • Solution: Ensure Docker is running
  • Check with: docker ps

“No workflows found”

  • Solution: Ensure you’re in a repo with .github/workflows/ directory
  • Verify with: ls .github/workflows/

“Action not found”

  • Some marketplace actions only work on GitHub
  • Check action documentation for local support
  • Consider using alternative actions or mocking

Slow first run:

  • act downloads Docker images on first run
  • Subsequent runs use cached images and are fast
  • Choose “Medium” image size for best balance

What Works Locally

Runs successfully:

  • Checkout code
  • Install dependencies
  • Run tests
  • Build artifacts
  • Run scripts
  • Use most marketplace actions
  • Job dependencies and ordering

Won’t work locally:

  • Upload artifacts to GitHub
  • Create releases
  • Comment on PRs/issues
  • OIDC token authentication (AWS, Azure, GCP)
  • GitHub API write operations
  • Some GitHub-specific actions

Workflow Integration

Pre-push validation:

# Add to your workflow
git add .
act
git commit -m "Add feature"
git push

Git hook automation:

Create .git/hooks/pre-push:

#!/bin/bash
echo "Validating workflows with act..."
act -q

Make it executable:

chmod +x .git/hooks/pre-push

Now workflows validate automatically before every push.

Tips

  • Start small: Test individual jobs with act -j before running full workflows
  • Use .secrets file: Avoid typing secrets repeatedly
  • Check logs carefully: act output differs slightly from GitHub’s UI
  • Keep Docker images updated: docker pull the images act uses periodically
  • Skip slow jobs locally: Use if: github.event_name != 'workflow_dispatch' to skip jobs in local runs

Parallelisation

concurrency:
  group: ${{ github.ref }}
  cancel-in-progress: true

Workload Identity