💡
Hey — It's Sandip Das 👋

It’s time to level up with advanced features like advanced matrix builds, job dependencies, reusable workflows, caching, artifacts, secrets, and environments.

These capabilities help you scale your automation for real-world DevOps and CI/CD pipelines.

💡Learning points:

🔹 Understanding job dependencies, needs, and parallel execution
🔹 Using matrix builds to test across multiple versions/environments
🔹 Creating reusable workflows across multiple repos
🔹 Advanced secrets and environment protections
🔹 Implementing caching for faster workflows (actions/cache)
🔹 Uploading & downloading artifacts (e.g., reports, logs, builds)
🔹 Using outputs, env, run, if, and continue-on-error smartly
🔹 Triggering workflows from other workflows (workflow chaining)
🔹 Manual approvals & policies

🧠 Learn here:

Download a high-resolution copy of this diagram here for future reference.

🏠 Jobs: Parallelism and Dependencies

Jobs:

  • Workflows have multiple jobs.
  • By default: All jobs run in parallel.
  • Control execution order with needs keyword.

Define Dependencies:

jobs:
  job_a:
    runs-on: ubuntu-latest
    steps:
      - run: echo "This is Job A"

  job_b:
    needs: job_a
    runs-on: ubuntu-latest
    steps:
      - run: echo "This is Job B"

job_b runs only after job_a succeeds.

Chaining Dependencies:

jobs:
  job_a: ...
  job_b: needs: job_a
  job_c: needs: job_b

Fan-out/Fan-in:

jobs:
  build: ...
  lint: ...
  test:
    needs: [build, lint]

test runs after both build and lint succeed.

Pure Parallel Execution:

jobs:
  job1: ...
  job2: ...

Sequential Execution:

jobs:
  job1: ...
  job2:
    needs: job1

🌍 Matrix Builds

Matrix builds help run jobs across multiple versions/environments.

Basic Matrix Syntax:

jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        node: [14, 16, 18]
    name: Node.js ${{ matrix.node }} tests
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node }}
      - run: npm install
      - run: npm test

Result: 3 parallel jobs (Node 14, 16, 18).

Cross OS + Versions Example:

strategy:
  matrix:
    os: [ubuntu-latest, windows-latest, macos-latest]
    python: [3.8, 3.9, 3.10]

→ 9 combinations (3 OS x 3 Python versions).


Creating Reusable Workflows

Step 1: Reusable Workflow (example in org/workflows/.github/workflows/deploy.yml)

name: Reusable Deploy Workflow
on:
  workflow_call:
    inputs:
      environment:
        required: true
        type: string
    secrets:
      DEPLOY_TOKEN:
        required: true
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - run: |
          echo "Environment: ${{ inputs.environment }}"
          echo "Using token"

Step 2: Caller Workflow (other repo)

jobs:
  call-deploy:
    uses: org/workflows/.github/workflows/deploy.yml@main
    with:
      environment: production
    secrets:
      DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}

🔒 Advanced Secrets and Environment Protections

Levels of Secrets:

  • Repository level
  • Organization level
  • Environment level (best for staging/production)

Protections:

  • Manual approval (reviewers)
  • Branch restrictions
  • Wait timers

Example:

jobs:
  deploy:
    runs-on: ubuntu-latest
    environment:
      name: production
    steps:
      - run: ./deploy.sh
        env:
          DEPLOY_KEY: ${{ secrets.DEPLOY_KEY }}

Implementing Caching with actions/cache

Why Cache?

  • Reuse node_modules, .cache, etc.
  • Faster builds, reduced CI time.

Example for npm:

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

Other examples:

  • pip caching: ~/.cache/pip
  • Go modules: ~/.cache/go-build, ~/go/pkg/mod

📂 Uploading and Downloading Artifacts

Uploading Example:

- name: Upload test results
  uses: actions/upload-artifact@v4
  with:
    name: test-results
    path: ./reports/

Downloading Example:

- name: Download artifact
  uses: actions/download-artifact@v4
  with:
    name: my-binary

Retention Setting:

- name: Upload logs
  uses: actions/upload-artifact@v4
  with:
    name: logs
    path: ./logs/
    retention-days: 7

🌟 Smart Use of outputs, env, run, if, continue-on-error

Concept Usage
outputs Share data across jobs/steps
env Set environment variables globally/per step
run Run shell commands
if Conditionally run steps/jobs
continue-on-error Ignore step failure

Example:

- run: ./flaky-script.sh
  continue-on-error: true

✅ Manual Approvals and Policies

Enable Manual Approvals:

  • Go to Settings > Environments > production
  • Add required reviewers

Branch Protection:

  • Restrict deployments only from certain branches (like main).
  • Add wait timers if needed.

📖 Learning Resources

📌 GitHub Actions Advanced Features
📌 Matrix Strategy Docs
📌 Reusable Workflows Guide
📌 Caching Dependencies
📌 Environments & Deployment Protection Rules
📌 Workflow Inputs and Outputs




🔥 Challenges

🔹 Challenge 1: Use a matrix to test across:

  • 3 versions of Node.js or Python
  • 2 OSs (ubuntu-latest, windows-latest)

🔹 Challenge 2: Use needs: to create a pipeline like:

  • build → test → deploy (conditionally run deploy if tests pass)

🔹 Challenge 3: Use actions/cache@v3 to store build dependencies and speed up CI

🔹 Challenge 4: Add workflow_dispatch inputs like environment: [dev, prod] and print them in the job

🔹 Challenge 5: Add an if: condition to only run a deployment job on the main branch

🔹 Challenge 6: Upload a build artifact in one job and download it in another

🔹 Challenge 7: Create a reusable workflow called from another workflow using workflow_call

🔹 Challenge 8: Define a protected environment with manual approval and use it in a deploy job

🔹 Challenge 9: Use output from one job (e.g., version, build_hash) in the next job

🔹 Challenge 10: Use a GitHub Action to build a Docker image, tag it, and push to Docker Hub


Solutions:

✅ Challenge 1: Matrix Testing Across Versions and OS

jobs:
  test:
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os: [ubuntu-latest, windows-latest]
        node: [14, 16, 18]
    steps:
      - uses: actions/checkout@v3
      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: ${{ matrix.node }}
      - run: node -v

✅ Challenge 2: needs: Build → Test → Deploy

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - run: echo "Building..."

  test:
    runs-on: ubuntu-latest
    needs: build
    steps:
      - run: echo "Testing..."

  deploy:
    runs-on: ubuntu-latest
    needs: test
    if: ${{ success() }}
    steps:
      - run: echo "Deploying..."

✅ Challenge 3: Caching Build Dependencies

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

✅ Challenge 4: Add workflow_dispatch Inputs

on:
  workflow_dispatch:
    inputs:
      environment:
        description: 'Environment to deploy to'
        required: true
        default: 'dev'
        type: choice
        options:
          - dev
          - prod

jobs:
  print-env:
    runs-on: ubuntu-latest
    steps:
      - run: echo "Deploying to ${{ github.event.inputs.environment }} environment."

✅ Challenge 5: Deploy Only on Main Branch

jobs:
  deploy:
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'
    steps:
      - run: echo "Deploying from main branch."

✅ Challenge 6: Upload and Download Build Artifact

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - run: echo "Build Output" > build.txt
      - uses: actions/upload-artifact@v3
        with:
          name: build-artifact
          path: build.txt

  deploy:
    runs-on: ubuntu-latest
    needs: build
    steps:
      - uses: actions/download-artifact@v3
        with:
          name: build-artifact
      - run: cat build.txt

✅ Challenge 7: Reusable Workflow with workflow_call

reusable-workflow.yml

on:
  workflow_call:

jobs:
  echo:
    runs-on: ubuntu-latest
    steps:
      - run: echo "Reusable Workflow Running"

main-workflow.yml

jobs:
  call-reusable:
    uses: your-repo/.github/workflows/reusable-workflow.yml@main

✅ Challenge 8: Protected Environment with Manual Approval

jobs:
  deploy:
    runs-on: ubuntu-latest
    environment:
      name: production
      url: https://your-app-url.com
    steps:
      - run: echo "Deploying after manual approval!"
Make sure to configure production environment protection rules inside GitHub repo settings.

✅ Challenge 9: Pass Output Between Jobs

jobs:
  build:
    runs-on: ubuntu-latest
    outputs:
      build_hash: ${{ steps.hash.outputs.hash }}
    steps:
      - id: hash
        run: echo "hash=$(date +%s)" >> $GITHUB_OUTPUT

  deploy:
    runs-on: ubuntu-latest
    needs: build
    steps:
      - run: echo "Using build hash: ${{ needs.build.outputs.build_hash }}"

✅ Challenge 10: Build and Push Docker Image

jobs:
  docker:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Log in to Docker Hub
        run: echo "${{ secrets.DOCKER_PASSWORD }}" | docker login -u "${{ secrets.DOCKER_USERNAME }}" --password-stdin
      - name: Build Docker Image
        run: docker build -t yourusername/yourapp:latest .
      - name: Push Docker Image
        run: docker push yourusername/yourapp:latest


🤷🏻 How to Participate?

✅ Complete the tasks and challenges.
✅ Document your progress and key takeaways on GitHub ReadMe, Medium, or Hashnode.
✅ Share the above in a LinkedIn post tagging me (Sandip Das), and use #60DaysOfDevOps to engage with the community!


Share this post