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!
Member discussion