DevToolBoxGRATUIT
Blog

GitHub Actions CI/CD : Guide complet

14 minpar DevToolBox

GitHub Actions CI/CD: Complete Setup Guide

GitHub Actions is a powerful CI/CD platform built directly into GitHub that allows you to automate your build, test, and deployment workflows. With its deep integration into the GitHub ecosystem, extensive marketplace of reusable actions, and generous free tier, GitHub Actions has become the preferred choice for automating software delivery pipelines in 2026. This guide walks you through setting up production-ready CI/CD workflows from scratch.

Understanding GitHub Actions Concepts

Before writing your first workflow, you need to understand the core building blocks:

  • Workflow - An automated process defined in a YAML file that runs one or more jobs. Stored in .github/workflows/
  • Event - A trigger that starts a workflow (push, pull request, schedule, manual dispatch, etc.)
  • Job - A set of steps that execute on the same runner. Jobs run in parallel by default
  • Step - An individual task within a job. Can be a shell command or a reusable action
  • Action - A reusable unit of code that performs a specific task (checkout code, set up Node.js, deploy, etc.)
  • Runner - A server that executes your workflows. GitHub provides hosted runners (Ubuntu, macOS, Windows) or you can use self-hosted runners
  • Artifact - Files produced by a workflow that can be shared between jobs or downloaded after completion

Your First CI Workflow

Let us start with a basic CI workflow for a Node.js project that runs on every push and pull request. Create a file at .github/workflows/ci.yml in your repository:

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

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

jobs:
  test:
    name: Test
    runs-on: ubuntu-latest

    strategy:
      matrix:
        node-version: [18, 20, 22]

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

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

      - name: Install dependencies
        run: npm ci

      - name: Run linter
        run: npm run lint

      - name: Run type checking
        run: npm run type-check

      - name: Run tests
        run: npm test -- --coverage

      - name: Upload coverage report
        if: matrix.node-version == 20
        uses: actions/upload-artifact@v4
        with:
          name: coverage-report
          path: coverage/
          retention-days: 7

This workflow checks out your code, installs dependencies with caching, and runs linting, type checking, and tests across three Node.js versions in parallel. The matrix strategy automatically creates three separate job runs.

Build and Deploy Workflow

A complete CI/CD pipeline typically includes build, test, and deploy stages. Here is a workflow that builds a Docker image and deploys to different environments based on the branch:

# .github/workflows/deploy.yml
name: Build and Deploy

on:
  push:
    branches: [main, staging]
  workflow_dispatch:
    inputs:
      environment:
        description: 'Target environment'
        required: true
        type: choice
        options:
          - staging
          - production

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

env:
  REGISTRY: ghcr.io
  IMAGE_NAME: ${{ github.repository }}

jobs:
  build:
    name: Build and Push Docker Image
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write

    outputs:
      image-tag: ${{ steps.meta.outputs.tags }}
      image-digest: ${{ steps.build.outputs.digest }}

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

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

      - name: Login to GitHub Container Registry
        uses: docker/login-action@v3
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Extract metadata
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
          tags: |
            type=sha,prefix=
            type=ref,event=branch

      - name: Build and push
        id: build
        uses: docker/build-push-action@v5
        with:
          context: .
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          cache-from: type=gha
          cache-to: type=gha,mode=max

  deploy-staging:
    name: Deploy to Staging
    needs: build
    if: github.ref == 'refs/heads/staging' || github.event.inputs.environment == 'staging'
    runs-on: ubuntu-latest
    environment: staging

    steps:
      - name: Deploy to staging
        run: |
          echo "Deploying ${{ needs.build.outputs.image-tag }} to staging"
          # Add your deployment commands here
          # e.g., kubectl set image, aws ecs update-service, etc.

  deploy-production:
    name: Deploy to Production
    needs: build
    if: github.ref == 'refs/heads/main' || github.event.inputs.environment == 'production'
    runs-on: ubuntu-latest
    environment:
      name: production
      url: https://myapp.example.com

    steps:
      - name: Deploy to production
        run: |
          echo "Deploying ${{ needs.build.outputs.image-tag }} to production"
          # Add your deployment commands here

Caching Dependencies

Caching is essential for fast CI pipelines. GitHub Actions provides built-in caching for most package managers. Here are caching strategies for popular ecosystems:

# Node.js - npm/pnpm/yarn caching
- uses: actions/setup-node@v4
  with:
    node-version: 20
    cache: 'npm'              # or 'pnpm' or 'yarn'

# Python - pip caching
- uses: actions/setup-python@v5
  with:
    python-version: '3.12'
    cache: 'pip'

# Go - module caching
- uses: actions/setup-go@v5
  with:
    go-version: '1.22'
    cache: true

# Rust - cargo caching
- uses: actions/cache@v4
  with:
    path: |
      ~/.cargo/bin/
      ~/.cargo/registry/index/
      ~/.cargo/registry/cache/
      ~/.cargo/git/db/
      target/
    key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
    restore-keys: |
      ${{ runner.os }}-cargo-

# Generic caching (any directory)
- uses: actions/cache@v4
  with:
    path: .build-cache
    key: build-${{ runner.os }}-${{ hashFiles('src/**') }}
    restore-keys: |
      build-${{ runner.os }}-

Secrets and Environment Variables

Sensitive data like API keys, deployment credentials, and tokens should never be stored in your workflow files. GitHub provides encrypted secrets at the repository, environment, and organization level.

# Setting secrets in GitHub:
# Repository Settings > Secrets and variables > Actions > New repository secret

# Using secrets in workflows
jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: production    # Uses environment-specific secrets
    steps:
      - name: Deploy
        env:
          API_KEY: ${{ secrets.API_KEY }}
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
        run: |
          echo "Deploying with environment-specific credentials"

# Environment variables at different levels
env:                          # Workflow-level: available to all jobs
  NODE_ENV: production

jobs:
  build:
    env:                      # Job-level: available to all steps in this job
      BUILD_TARGET: linux
    steps:
      - name: Build
        env:                  # Step-level: available only in this step
          VERBOSE: true
        run: npm run build

Reusable Workflows

As your CI/CD pipelines grow, you can extract common patterns into reusable workflows. This follows the DRY principle and makes maintenance easier across multiple repositories.

# .github/workflows/reusable-test.yml
name: Reusable Test Workflow

on:
  workflow_call:
    inputs:
      node-version:
        description: 'Node.js version'
        required: false
        type: string
        default: '20'
      run-e2e:
        description: 'Run E2E tests'
        required: false
        type: boolean
        default: false
    secrets:
      CODECOV_TOKEN:
        required: false

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ inputs.node-version }}
          cache: 'npm'
      - run: npm ci
      - run: npm test -- --coverage
      - name: E2E tests
        if: inputs.run-e2e
        run: npm run test:e2e

---
# .github/workflows/ci.yml - Calling the reusable workflow
name: CI

on:
  pull_request:
    branches: [main]

jobs:
  unit-tests:
    uses: ./.github/workflows/reusable-test.yml
    with:
      node-version: '20'
      run-e2e: false

  full-tests:
    uses: ./.github/workflows/reusable-test.yml
    with:
      node-version: '20'
      run-e2e: true
    secrets:
      CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}

Pull Request Automation

GitHub Actions excels at automating pull request workflows. Here is a workflow that runs checks, adds labels, and posts comments on PRs:

# .github/workflows/pr-checks.yml
name: PR Checks

on:
  pull_request:
    types: [opened, synchronize, reopened]

permissions:
  contents: read
  pull-requests: write

jobs:
  size-label:
    name: PR Size Label
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Calculate PR size
        id: size
        run: |
          ADDITIONS=$(git diff --numstat origin/main...HEAD | awk '{s+=$1} END {print s}')
          DELETIONS=$(git diff --numstat origin/main...HEAD | awk '{s+=$2} END {print s}')
          TOTAL=$((ADDITIONS + DELETIONS))
          echo "total=$TOTAL" >> $GITHUB_OUTPUT
          if [ $TOTAL -lt 50 ]; then echo "label=size/S" >> $GITHUB_OUTPUT
          elif [ $TOTAL -lt 200 ]; then echo "label=size/M" >> $GITHUB_OUTPUT
          elif [ $TOTAL -lt 500 ]; then echo "label=size/L" >> $GITHUB_OUTPUT
          else echo "label=size/XL" >> $GITHUB_OUTPUT
          fi

  lint-commit:
    name: Lint Commit Messages
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - name: Check conventional commits
        run: |
          git log --format=%s origin/main..HEAD | while read msg; do
            if ! echo "$msg" | grep -qE "^(feat|fix|docs|style|refactor|test|chore|ci|build|perf)(\(.+\))?: .+"; then
              echo "Invalid commit message: $msg"
              echo "Expected format: type(scope): description"
              exit 1
            fi
          done

Scheduled Workflows

GitHub Actions supports cron-based scheduling for tasks like dependency updates, nightly builds, stale issue cleanup, and periodic health checks.

# .github/workflows/scheduled.yml
name: Scheduled Tasks

on:
  schedule:
    - cron: '0 2 * * 1'     # Every Monday at 2:00 AM UTC
  workflow_dispatch:          # Allow manual trigger

jobs:
  dependency-audit:
    name: Security Audit
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
      - run: npm ci
      - name: Run security audit
        run: npm audit --production
        continue-on-error: true

  stale-issues:
    name: Close Stale Issues
    runs-on: ubuntu-latest
    permissions:
      issues: write
      pull-requests: write
    steps:
      - uses: actions/stale@v9
        with:
          stale-issue-message: >
            This issue has been automatically marked as stale
            because it has not had activity in 60 days.
          days-before-stale: 60
          days-before-close: 14

Deploying to Vercel, Netlify, and AWS

Here are deployment steps for popular hosting platforms:

# Deploy to Vercel
- name: Deploy to Vercel
  env:
    VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}
    VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
    VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}
  run: |
    npm i -g vercel
    vercel pull --yes --token=$VERCEL_TOKEN
    vercel build --prod --token=$VERCEL_TOKEN
    vercel deploy --prebuilt --prod --token=$VERCEL_TOKEN

# Deploy to AWS S3 + CloudFront
- name: Configure AWS credentials
  uses: aws-actions/configure-aws-credentials@v4
  with:
    aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
    aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
    aws-region: us-east-1

- name: Deploy to S3
  run: aws s3 sync ./dist s3://my-bucket --delete

- name: Invalidate CloudFront
  run: |
    aws cloudfront create-invalidation \
      --distribution-id ${{ secrets.CF_DISTRIBUTION_ID }} \
      --paths "/*"

# Deploy to AWS ECS
- name: Deploy to ECS
  run: |
    aws ecs update-service \
      --cluster my-cluster \
      --service my-service \
      --force-new-deployment

Workflow Security Best Practices

  • Pin action versions to SHA - Use uses: actions/checkout@abc123 instead of @v4 to prevent supply chain attacks
  • Set minimum permissions - Use permissions to grant only the access each job needs
  • Use environment protection rules - Require manual approval for production deployments
  • Never echo secrets - GitHub masks secrets in logs, but avoid printing them in commands
  • Audit third-party actions - Review the source code of actions before using them, especially for sensitive operations
  • Use OIDC for cloud providers - Use OpenID Connect instead of long-lived access keys for AWS, GCP, and Azure
  • Limit workflow triggers - Be cautious with pull_request_target as it runs with write access on forked PRs
  • Set concurrency groups - Prevent parallel deployments that can cause race conditions

Debugging Failed Workflows

When workflows fail, use these techniques to diagnose and fix issues:

# Enable debug logging
# Set repository secret: ACTIONS_RUNNER_DEBUG = true
# Set repository secret: ACTIONS_STEP_DEBUG = true

# Add debug output in steps
- name: Debug info
  run: |
    echo "GitHub ref: ${{ github.ref }}"
    echo "GitHub event: ${{ github.event_name }}"
    echo "Runner OS: ${{ runner.os }}"
    echo "Working directory: $(pwd)"
    ls -la

# Use tmate for interactive debugging (SSH into runner)
- name: Setup tmate session
  if: failure()
  uses: mxschmitt/action-tmate@v3
  timeout-minutes: 15

# Retry flaky steps
- name: Flaky E2E test
  uses: nick-fields/retry@v3
  with:
    timeout_minutes: 10
    max_attempts: 3
    command: npm run test:e2e

Frequently Asked Questions

How much does GitHub Actions cost?

GitHub Actions is free for public repositories with unlimited minutes. For private repositories, free accounts get 2,000 minutes per month, Pro gets 3,000, and Team gets 3,000. Minutes are multiplied for macOS (10x) and Windows (2x) runners. Self-hosted runners are always free regardless of repository visibility.

What is the difference between push and pull_request triggers?

The push event triggers when commits are pushed to a branch. The pull_request event triggers when a PR is opened, updated, or reopened. For CI, use pull_request to validate changes before merge. For CD, use push to the main branch to trigger deployments after merge.

How do I pass data between jobs?

Use outputs for small values (strings, IDs, flags) and artifacts for files (build outputs, test reports, Docker images). Outputs are set with echo "key=value" >> $GITHUB_OUTPUT and referenced with needs.job-name.outputs.key. Artifacts are uploaded with actions/upload-artifact and downloaded with actions/download-artifact.

Can I run workflows locally for testing?

Yes, use the act tool (github.com/nektos/act) to run GitHub Actions workflows locally in Docker containers. It supports most features including secrets, matrix builds, and artifacts. Install with brew install act and run with act push or act pull_request.

How do I trigger a workflow manually?

Add workflow_dispatch to your workflow's on section. You can define inputs with types (string, choice, boolean) that appear in the GitHub UI. Trigger from the Actions tab, the GitHub CLI (gh workflow run), or the REST API.

𝕏 Twitterin LinkedIn
Cet article vous a-t-il aidé ?

Restez informé

Recevez des astuces dev et les nouveaux outils chaque semaine.

Pas de spam. Désabonnez-vous à tout moment.

Articles connexes

Guide Kubernetes pour débutants

Apprenez les fondamentaux de Kubernetes : Pods, Deployments, Services et commandes kubectl.

Tutoriel Docker Compose : des bases aux stacks prets pour la production

Tutoriel complet Docker Compose : syntaxe docker-compose.yml, services, reseaux, volumes, variables d'environnement, healthchecks et exemples Node.js/Python/WordPress.