This content originally appeared on Level Up Coding - Medium and was authored by Heba Aly
Choosing the Right Approach for Your Project
GitHub Actions provides us with different strategies to approach CI/CD. All we need to do is choose the right one for our case.
What is a Strategy in GitHub Actions?
A strategy in GitHub Actions is a job-level configuration that controls how a job executes, including whether it runs multiple times and how failures are handled.
Types of Strategies:-
Simple Linear workflow strategy
It’s a single job workflow that can have multiple steps, and all of them run sequentially.
name: Simple CI
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
- run: npm install
- run: npm test
- run: npm run build
Here, as you can see, there’s only one job test that has numerous steps. These steps will be run sequentially.
If one step fails, the entire workflow fails, and this is reflected in GitHub Actions when it runs.

We can use this strategy if it’s a small, non-complex project, or if it’s a prototype to prove a concept, or if the team is new to CI/CD and wants to start with something simple.
It’s easy to understand and fast to set up, but scaling can be challenging. If one step fails, the entire workflow fails, and, of course, there is no parallelization because steps run sequentially.
Matrix Strategy
In the matrix strategy, we can run the same job across multiple configurations simultaneously.
name: Matrix Testing
on: [push, pull_request]
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
node-version: [16, 18, 20]
steps:
- uses: actions/checkout@v4
- name: Setup Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- run: npm install
- run: npm test
Here, we run the job test multiple times with different combinations of OS and Node.js versions, which means the job will run nine times, and each time it will have one OS and one Node.js version (e.g., ubuntu-latest with Node version 16, then ubuntu-latest with Node version 18, and so on, until macos-latest with Node version 20).
The job executions, in a matrix strategy, run in parallel, which fixes the issue of a simple linear workflow.



We can use this strategy when we have cross-platform or language-specific requirements, or when we need to test across multiple environments.
Matrix strategy also helps us detect issues with specific configuration combinations early.
Of course, with a matrix strategy, we consume more resources, and debugging is more complex.
Multi-Job Pipeline Strategy
Instead of having one job that includes all the steps, we break it down into distinct jobs that may depend on each other and run in sequence or parallel.
name: Multi-Job Pipeline
on: [push, pull_request]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm install
- run: npm run lint
test:
runs-on: ubuntu-latest
needs: lint
steps:
- uses: actions/checkout@v4
- run: npm install
- run: npm test
build:
runs-on: ubuntu-latest
needs: [lint, test]
steps:
- uses: actions/checkout@v4
- run: npm install
- run: npm run build
deploy:
runs-on: ubuntu-latest
needs: build
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4
- run: npm run deploy
Here, we have four jobs. lint , test , build , and deploy
Job test will run after the job lint is completed successfully, which is decided by the needs statement needs: lint.
Job build will run after jobs lint , and test are completed, which is decided by the needs statement needs: [lint, test] .
Job deploy will run after jobs build , which is decided by the needs statement needs: build . This step will be executed only if we push to the main branch, which is decided by the if statement if: github.ref == 'refs/heads/main .
Without needs In each job, the jobs will run in parallel.



We can use this strategy when we need separation of concerns, different jobs require different environments, or we want to stop the workflow on the first error. Additionally, it’s useful for complex applications with multiple stages.
This approach provides clear pipeline visualization and efficient resource usage, allowing us to apply parallel execution when possible.
The only thing that can get complex is job dependencies, which can be bottlenecks, and passing artifacts and variables between resources needs to be set up.
Monorepo Strategy
A monorepo is a repository that contains multiple apps or packages within a single codebase.
A monorepo strategy involves configuring workflows to run specific tasks based on which app or package has been changed, so we only run them on what’s affected.
name: Monorepo CI
on: [push, pull_request]
jobs:
changes:
runs-on: ubuntu-latest
outputs:
frontend: ${{ steps.changes.outputs.frontend }}
backend: ${{ steps.changes.outputs.backend }}
steps:
- uses: actions/checkout@v4
- uses: dorny/paths-filter@v2
id: changes
with:
filters: |
frontend:
- 'packages/frontend/**'
backend:
- 'packages/backend/**'
frontend:
needs: changes
if: ${{ needs.changes.outputs.frontend == 'true' }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm install
- run: npm run test:frontend
backend:
needs: changes
if: ${{ needs.changes.outputs.backend == 'true' }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm install
- run: npm run test:backend
Here, we can see that the workflow is used to automate specific tasks in a monorepo that contains frontend and backend apps; the workflow consists of three jobs. changes , frontend , and backend .
changes Checks whether changes occur in the frontend, backend app, or both, and returns the values as output.
backend and frontend have a dependency on changes by checking the output values of changes . That was done on these two statements. if: ${{ needs.changes.outputs.frontend == 'true' }} , if: ${{ needs.changes.outputs.backend == 'true' }} .


We can use this strategy when we have multiple services, micro-frontend or micro-architecture, when teams work on different parts independently, or with a large codebase where builds are expensive.
It’s a good strategy for resource usage and scales well with repository size.
However, it can be challenging when it comes to setup and maintenance, as the path filter can become complex, and it may miss integration tests.
Environment-Based Strategy
It involves different workflows or job configurations for various deployment environments.
name: Environment Deploy
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
deploy-staging:
if: github.ref == 'refs/heads/develop'
runs-on: ubuntu-latest
environment: staging
steps:
- uses: actions/checkout@v4
- run: npm run deploy:staging
deploy-production:
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
environment: production
steps:
- uses: actions/checkout@v4
- run: npm run deploy:production
Here, we have two jobs deploy-staging , and deploy-production , and based on which branch is updated, the workflow is deployed to a specific environment.


This strategy can be beneficial when we have multiple deployment environments, each with its own unique configurations and approval levels.
Additionally, this strategy allows for environmental isolation and a clear deployment history.
However, we can face issues with configuration duplication and complex branch management.
Reusable Workflow Strategy
So we create centralized, reusable workflows that can be called from multiple repositories.
# .github/workflows/reusable-ci.yml
name: Reusable CI
on:
workflow_call:
inputs:
node-version:
required: false
type: string
default: '18'
secrets:
NPM_TOKEN:
required: true
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ inputs.node-version }}
- run: npm install
- run: npm test
# Consumer workflow
name: Use Reusable
on: [push]
jobs:
call-reusable:
uses: ./.github/workflows/reusable-ci.yml
with:
node-version: '20'
secrets:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
Here, we have two workflows, one is responsible for running only tests, and the other is used to run them.

It can be helpful when we want to standardize our workflows, and if we have multiple repositories with the same CI/CD steps.
It reduces duplication and maintains consistency, making maintenance and improvement easier with this approach.
Challenges can be complex versioning if needed, the first time creation can be complex, and less flexible with unique requirements.
Feature Flag Strategy
It’s when we use conditions and feature flags to control the workflow behavior.
name: Feature Flag CI
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run standard tests
run: npm test
- name: Run experimental tests
if: contains(github.event.head_commit.message, '[experimental]')
run: npm run test:experimental
- name: Performance tests
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
run: npm run test:performance
We have observed similar conditions in previous workflows, which are also present here.
This way can be used with A/B tests if we have different configurations for each trigger or optional steps.
It’s a flexible workflow, ideal for experimentation and the gradual adoption of new features, as well as conditional resource usage.
Challenges can be unpredictable testing behaviors and can become complex quickly.
Conclusion
Which strategy is better? It depends on several factors, including project size and complexity, team structure, performance requirements, and deployment needs.
The good approach is to start simple and grow based on your needs.
You can start with a simple linear workflow, then transition to a multi-job workflow for increased complexity, and finally begin using a reusable workflow for scaling.
Thanks for reading :)
GitHub Actions Strategy Guide was originally published in Level Up Coding on Medium, where people are continuing the conversation by highlighting and responding to this story.
This content originally appeared on Level Up Coding - Medium and was authored by Heba Aly

Heba Aly | Sciencx (2025-07-30T04:06:04+00:00) GitHub Actions Strategy Guide. Retrieved from https://www.scien.cx/2025/07/30/github-actions-strategy-guide/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.