Skip to content

GitHub Actions — Workflow Syntax & Best Practices Guide

DodaTech Updated 2026-06-24 5 min read

In this tutorial, you'll learn about GitHub Actions. We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.

GitHub Actions is a CI/CD platform integrated into GitHub repositories that lets you automate build, test, and deployment workflows defined in YAML files stored in .github/workflows/.

What You'll Learn

Why It Matters

GitHub Actions eliminates the need for separate CI tools by embedding pipeline configuration directly in your Repository. Workflow files are version-controlled, automatically triggered by GitHub events, and surfaced as check runs on pull requests. DodaTech uses GitHub Actions across 120+ public and private repositories, reducing average CI run time by 60% through parallel matrix builds and intelligent caching.

Real-World Use

DodaZIP's open-source repositories use GitHub Actions to run linting across 3 OS platforms, execute 2,000+ tests in parallel matrix builds, publish npm packages triggered by version tags, and deploy documentation to GitHub Pages — all without maintaining a single CI server.

flowchart TD
    A[Git Push / PR] --> B[Event Trigger]
    B --> C[.github/workflows/ci.yml]
    C --> D[Job: lint]
    C --> E[Job: build]
    C --> F[Job: test]
    E --> G[Matrix: os x node-version]
    F --> G
    D --> H[Job: deploy]
    G --> H
    H --> I[Production / Pages]
    style C fill:#2088ff,color:#fff
â„šī¸ Info

Prerequisites: A GitHub account. Basic understanding of YAML syntax.

Workflow Structure

name: CI Pipeline

on:
  push:
    branches: [main, develop]
    paths-ignore:
      - '**.md'
      - 'docs/**'
  pull_request:
    branches: [main]
    types: [opened, synchronize, reopened]
  schedule:
    - cron: '0 6 * * 1'
  workflow_dispatch:
    inputs:
      environment:
        description: 'Deployment target'
        required: true
        default: staging
        type: choice
        options:
          - staging
          - production

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

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

defaults:
  run:
    shell: bash

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_VERSION }}
      - run: npm ci
      - run: npm run lint

  test:
    runs-on: ${{ matrix.os }}
    needs: lint
    strategy:
      matrix:
        os: [ubuntu-latest, windows-latest, macos-latest]
        node-version: [18, 20, 22]
        include:
          - os: ubuntu-latest
            node-version: 22
            coverage: true
      fail-fast: false
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node-version }}
      - name: Cache npm
        uses: actions/cache@v3
        with:
          path: ~/.npm
          key: ${{ runner.os }}-node-${{ hashFiles('package-lock.json') }}
      - run: npm ci
      - run: npm test
      - if: matrix.coverage
        uses: codecov/codecov-action@v3

  build:
    runs-on: ubuntu-latest
    needs: test
    steps:
      - uses: actions/checkout@v4
      - run: npm ci
      - run: npm run build
      - uses: actions/upload-artifact@v3
        with:
          name: build-output
          path: dist/

  deploy:
    runs-on: ubuntu-latest
    needs: build
    if: github.ref == 'refs/heads/main' && github.event_name == 'push'
    environment:
      name: production
      url: https://app.dodatech.com
    steps:
      - uses: actions/checkout@v4
      - uses: actions/download-artifact@v3
        with:
          name: build-output
          path: dist/
      - uses: peaceiris/actions-gh-pages@v3
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          publish_dir: ./dist

Reusable Workflows

# .github/workflows/reusable-build.yml
on:
  workflow_call:
    inputs:
      node-version:
        required: true
        type: string
      build-command:
        required: false
        type: string
        default: 'npm run build'
    secrets:
      registry-token:
        required: true

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ inputs.node-version }}
          registry-url: https://npm.pkg.github.com
      - run: npm ci
      - run: ${{ inputs.build-command }}
      - run: npm publish
        env:
          NODE_AUTH_TOKEN: ${{ secrets.registry-token }}
# .github/workflows/service-a.yml
name: Service A Pipeline

jobs:
  call-build:
    uses: dodatech/shared-workflows/.github/workflows/reusable-build.yml@v1
    with:
      node-version: '22'
      build-command: 'npm run build:prod'
    secrets:
      registry-token: ${{ secrets.GITHUB_TOKEN }}

OIDC Authentication for Cloud Providers

jobs:
  deploy-aws:
    runs-on: ubuntu-latest
    permissions:
      id-token: write
      contents: read
    steps:
      - uses: actions/checkout@v4
      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::123456789012:role/GitHubActionsDeploy
          aws-region: us-east-1
      - run: aws ecs update-service --cluster prod --service myapp --force-new-deployment

  deploy-gcp:
    runs-on: ubuntu-latest
    permissions:
      id-token: write
      contents: read
    steps:
      - uses: actions/checkout@v4
      - id: auth
        uses: google-github-actions/auth@v2
        with:
          workload_identity_provider: projects/123456789/locations/global/workloadIdentityPools/github-pool/providers/github-provider
          service_account: deploy-sa@dodatech.iam.gserviceaccount.com
      - run: gcloud run deploy myapp --image us.gcr.io/dodatech/myapp:latest

Matrix Builds with Includes

jobs:
  build:
    strategy:
      matrix:
        os: [ubuntu-latest]
        node: [18, 20, 22]
        include:
          - os: windows-latest
            node: 20
          - os: macos-latest
            node: 20
    runs-on: ${{ matrix.os }}
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node }}
      - run: npm ci
      - run: npm test

Environment Protection Rules

name: Production Deploy

on:
  workflow_dispatch:

jobs:
  deploy:
    runs-on: ubuntu-latest
    environment:
      name: production
      url: https://app.dodatech.com
    steps:
      - uses: actions/checkout@v4
      - run: ./deploy.sh

Configure environment protection in GitHub (Settings > Environments > production): require manual approval from at least 2 reviewers, restrict branch to main, and add secrets scoped only to this environment.

Common Configuration Mistakes

  1. Running all tests on every push without path filters: Documentation-only PRs trigger unnecessary CI runs. Use paths-ignore: ['**.md'] to skip irrelevant changes.

  2. Not using concurrency to cancel duplicate runs: Pushing multiple commits rapidly creates parallel pipeline executions. concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancels in-progress runs for the same branch.

  3. Hardcoding credentials instead of using secrets: API keys, tokens, and passwords should use ${{ secrets.NAME }}, never plain text in YAML files.

  4. Missing permissions for OIDC workflows: OIDC authentication requires explicit permissions: id-token: write at the job level. Without it, token exchange fails.

  5. Not setting fail-fast: false on matrix builds: When fail-fast is true (default), one failing matrix combination cancels all others. Set fail-fast: false for comprehensive test results.

Practice Questions

  1. What triggers a GitHub Actions workflow? Answer: Workflows are triggered by GitHub events (push, pull_request, schedule, workflow_dispatch, release, etc.) defined in the on: block.

  2. How do matrix builds speed up CI? Answer: Matrix builds run identical test suites across multiple OS and version combinations in parallel, completing all variations in the time of one sequential run.

  3. What is the purpose of reusable workflows? Answer: Reusable workflows (workflow_call) allow defining shared pipeline logic that multiple repositories can call, reducing duplication and standardizing CI/CD patterns.

  4. How does environment protection work? Answer: Environments with required reviewers block deployment jobs until approved. Secrets are scoped to specific environments, preventing production credentials from being accessible in non-production contexts.

Challenge

Build a complete GitHub Actions CI/CD pipeline: run linting on push, execute matrix tests (Node 18/20/22 on ubuntu/Windows/macos), cache npm dependencies, build and publish a Docker image to GitHub Container Registry on main branch pushes, deploy to production with approval gate, and send Slack notifications on failure.

Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro