Skip to content
GitHub Actions Guide — CI/CD Workflows for Modern Development

GitHub Actions Guide — CI/CD Workflows for Modern Development

DodaTech Updated Jun 7, 2026 7 min read

GitHub Actions is a CI/CD platform that lets you automate build, test, and deployment workflows directly from your GitHub repository — triggered by pushes, pull requests, issue comments, or scheduled intervals.

What You’ll Learn

  • Writing workflow files with jobs, steps, and triggers
  • Using matrix builds to test across multiple versions and platforms
  • Leveraging the Actions Marketplace for reusable actions
  • Managing secrets and environment variables securely
  • Setting up self-hosted runners for custom environments
  • Caching dependencies to speed up workflows

Why GitHub Actions Matters

Separate CI/CD tools (Jenkins, CircleCI, TravisCI) require configuration outside your repository, separate UIs, and additional maintenance. GitHub Actions lives inside your repository — workflow files are version-controlled alongside your code, triggers are defined in YAML, and results appear on pull requests automatically. DodaZIP uses GitHub Actions to run linting, type checking, and tests on every pull request — preventing broken code from merging into the main branch and deploying releases automatically when tags are pushed.

    flowchart LR
    A[JavaScript & Bash Basics] --> B[GitHub Actions]
    B --> C[Workflow Triggers]
    B --> D[Jobs & Steps]
    B --> E[Matrix Builds]
    B --> F[Secrets & Caching]
    C --> G[Push / PR / Schedule]
    D --> H[Parallel Execution]
    E --> I[Multi-OS Testing]
    style B fill:#2088ff,color:#fff
  
Prerequisites: Basic JavaScript and Bash knowledge. A GitHub account and a repository with code to test.

Core Concepts

Workflow File Structure

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

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]
  workflow_dispatch: # Manual trigger from GitHub UI

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

  test:
    runs-on: ubuntu-latest
    needs: lint  # Wait for lint to pass
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
      - run: npm ci
      - run: npm test

  deploy:
    if: github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    needs: [lint, test]
    steps:
      - uses: actions/checkout@v4
      - run: npm ci
      - run: npm run build
      - uses: peaceiris/actions-gh-pages@v3
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          publish_dir: ./dist

Output: The workflow runs on every push to main and on every pull request. The lint job runs first. test waits for lint. deploy waits for both and only runs on pushes to main.

Matrix Builds

Test across multiple Node versions and operating systems:

jobs:
  test:
    strategy:
      matrix:
        node-version: [18, 20, 22]
        os: [ubuntu-latest, windows-latest, macos-latest]
      fail-fast: false  # Continue other matrix jobs if one fails

    runs-on: ${{ matrix.os }}
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node-version }}
      - run: npm ci
      - run: npm test
      - name: Upload coverage
        uses: codecov/codecov-action@v3
        if: matrix.os == 'ubuntu-latest' && matrix.node-version == 20

Output: GitHub Actions runs tests across all 9 combinations (3 Node versions × 3 OSes) in parallel. The fail-fast: false flag continues running even if one combination fails. Coverage is uploaded only for the ubuntu/Node 20 combination.

Marketplace Actions

jobs:
  deploy:
    steps:
      # Official GitHub actions
      - uses: actions/checkout@v4     # Check out code
      - uses: actions/setup-node@v4   # Set up Node.js
      - uses: actions/cache@v3        # Cache dependencies
      - uses: actions/upload-artifact@v3  # Upload build artifacts

      # Community actions
      - uses: docker/login-action@v3          # Docker Hub login
      - uses: aws-actions/configure-aws-credentials@v4  # AWS auth
      - uses: peaceiris/actions-gh-pages@v3   # Deploy to GitHub Pages
      - uses: superfly/flyctl-actions@master  # Deploy to Fly.io
      - uses: slackapi/slack-github-action@v1 # Send Slack notifications

Secrets and Environment Variables

jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: production  # Optional: require approval
    steps:
      - uses: actions/checkout@v4

      # Secrets set in GitHub repo Settings > Secrets and variables
      - name: Deploy to server
        env:
          SSH_HOST: ${{ secrets.SSH_HOST }}
          SSH_USER: ${{ secrets.SSH_USER }}
          SSH_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
          NODE_ENV: production
        run: |
          echo "$SSH_KEY" > ssh_key
          chmod 600 ssh_key
          ssh -i ssh_key $SSH_USER@$SSH_HOST "cd /app && git pull && npm ci && npm run build"

Output: Secrets are encrypted at rest in GitHub, masked in logs, and never shown in plain text. Environment variables without ${{ secrets }} are visible in logs.

Caching Dependencies

jobs:
  build:
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20

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

      - run: npm ci

      - name: Cache build output
        uses: actions/cache@v3
        with:
          path: dist/
          key: ${{ runner.os }}-build-${{ github.sha }}

Output: The first run installs dependencies and saves them to the cache. Subsequent runs restore from cache — install drops from 60 seconds to 2 seconds. The cache key changes when package-lock.json changes.

Self-Hosted Runners

jobs:
  test:
    runs-on: self-hosted  # Your own machine
    steps:
      - uses: actions/checkout@v4
      - run: docker build -t myapp .
      - run: docker run myapp npm test
# Install a self-hosted runner on your machine
# Go to GitHub repo > Settings > Actions > Runners > Add runner
# Follow the platform-specific instructions

# Run the runner
./run.sh

# Output:
# √ Connected to GitHub
# √ Listening for Jobs

Self-hosted runners are useful for: Accessing private networks, using specific hardware (GPU), running long jobs without timeout limits, and reducing costs compared to GitHub-hosted runners.

Common Mistakes

  1. Not using actions/checkout@v4 as the first step: The checkout action pulls your code. Without it, subsequent steps have nothing to work with.

  2. Storing secrets in YAML files: Secrets should be set in GitHub’s Secrets UI, not hardcoded. Use ${{ secrets.NAME }} to reference them.

  3. Running all tests on every push: Use path filters (paths: or paths-ignore:) to skip workflows when only documentation changes.

  4. Not caching dependencies: Without caching, every CI run downloads all dependencies from scratch. Caching reduces install time by 90%+.

  5. Ignoring workflow concurrency: Without concurrency settings, pushing multiple commits quickly creates multiple simultaneous runs. Add concurrency: group: ${{ github.workflow }}-${{ github.ref }}.

Practice Questions

  1. What is the difference between on: push and on: pull_request? Answer: push triggers when code is pushed to a branch. pull_request triggers when a PR is opened or updated. Both are typically combined for complete CI coverage.

  2. How do matrix builds speed up testing? Answer: Matrix builds run the same test suite across multiple OS/version combinations in parallel, completing all variations in the time of one sequential run.

  3. What is the purpose of the needs keyword? Answer: needs establishes job dependencies. The job waits for all listed jobs to complete successfully before starting.

  4. When should you use self-hosted runners? Answer: When you need access to private networks, specific hardware (GPU, custom CPUs), or want to avoid GitHub-hosted runner costs and timeouts.

Challenge

Build a complete CI/CD pipeline: create a workflow that lints on push, runs matrix tests (Node 18/20/22 on ubuntu/windows), caches npm dependencies, deploys to GitHub Pages on main branch merges, sends a Slack notification on failure, and requires manual approval for production deployment via environments.

FAQ

Is GitHub Actions free?
: GitHub Actions is free for public repositories (2000 minutes/month for private repos free tier). Minutes reset monthly.
Can I use GitHub Actions with GitLab/Bitbucket?
: No. GitHub Actions only works with GitHub repositories. GitLab has GitLab CI, Bitbucket has Bitbucket Pipelines.
How do I debug a failing workflow?
: Add ACTIONS_STEP_DEBUG=true as a secret to enable debug logging. SSH into runners with mxschmitt/action-tmate for interactive debugging.
Can I run Docker containers in GitHub Actions?
: Yes. GitHub Actions supports Docker natively. Use services: for dependent services or docker run directly.
What limits do GitHub Actions have?
: 6 hours per job, 35 days log retention, 72 hours queue time, 500 MB artifact storage (free tier). Self-hosted runners have no time limits.

Try It Yourself

# Create a workflow in any GitHub repo
mkdir -p .github/workflows

cat > .github/workflows/hello.yml << 'EOF'
name: Hello World
on: [push]
jobs:
  hello:
    runs-on: ubuntu-latest
    steps:
      - run: echo "Hello from GitHub Actions!"
      - run: echo "Triggered by ${{ github.event_name }} on ${{ runner.os }}"
      - run: |
          echo "Workflow: ${{ github.workflow }}"
          echo "Branch: ${{ github.ref }}"
          echo "SHA: ${{ github.sha }}"
EOF

git add .github/workflows/hello.yml
git commit -m "Add hello workflow"
git push

Then go to the Actions tab in your GitHub repo to see the workflow run.

What’s Next

TopicDescription
Git
Version control for CI/CD workflows
Docker Compose
Containerized testing with Actions

Related topics: JavaScript, Bash, Docker, Git

What’s Next

Congratulations on completing this GitHub Actions tutorial! Here’s where to go from here:

  • Practice daily — Consistency is more important than long study sessions
  • Build a project — Apply what you learned by building something real
  • Explore related topics — Check out other tutorials in the same category
  • Join the community — Discuss with other learners and share your progress

Remember: every expert was once a beginner. Keep coding!

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

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro