GitHub Actions â Workflow Syntax & Best Practices Guide
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
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
Running all tests on every push without path filters: Documentation-only PRs trigger unnecessary CI runs. Use
paths-ignore: ['**.md']to skip irrelevant changes.Not using
concurrencyto 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.Hardcoding credentials instead of using secrets: API keys, tokens, and passwords should use
${{ secrets.NAME }}, never plain text in YAML files.Missing permissions for OIDC workflows: OIDC authentication requires explicit
permissions: id-token: writeat the job level. Without it, token exchange fails.Not setting
fail-fast: falseon matrix builds: Whenfail-fastis true (default), one failing matrix combination cancels all others. Setfail-fast: falsefor comprehensive test results.
Practice Questions
What triggers a GitHub Actions workflow? Answer: Workflows are triggered by GitHub events (
push,pull_request,schedule,workflow_dispatch,release, etc.) defined in theon:block.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.
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.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