Reducing CI pipeline time in GitHub Actions is essential for maintaining high developer velocity, improving feedback loops, and cutting down cloud resource costs. Hereβs a detailed approach with strategies and best practices:
jobs:
lint:
...
test:
...
build:
...
strategy:
matrix:
node: [16, 18, 20]
- uses: actions/cache@v4
with:
path: ~/.npm
key: $-node-$
paths
, paths-ignore
, or if:
conditionals to skip workflows on unrelated changes.on:
push:
paths:
- 'src/**'
- '.github/workflows/**'
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
Separate CI (build, test) from CD (deploy) workflows.
main
or manual dispatch.continue-on-error: false
(default) to halt jobs when a failure occurs.If build artifacts are required in later jobs (e.g., Docker images or binaries), upload them instead of rebuilding.
- uses: actions/upload-artifact@v4
- uses: actions/download-artifact@v4
pytest-xdist
, jest --runInBand
).In Docker-based workflows, optimize Dockerfile
with:
.dockerignore
to limit context sizeworkflow_dispatch
or repository_dispatch
for manually triggered or conditional long-running jobs.Optimization | Time Saved |
---|---|
Caching npm deps | ~30-90 seconds |
Skipping jobs using paths |
Up to 100% |
Parallel matrix testing | ~50-70% reduction |
Using self-hosted runners | ~10-30 seconds per job |
# File: .github/workflows/ci.yaml
name: CI Pipeline
on:
push:
branches: [main]
paths:
- 'src/**'
- '.github/workflows/**'
pull_request:
paths:
- 'src/**'
- '.github/workflows/**'
workflow_dispatch:
jobs:
lint:
name: Lint Code
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run Linter
run: npm run lint
test:
name: Run Unit Tests
runs-on: ubuntu-latest
strategy:
matrix:
node: [18, 20]
os: [ubuntu-latest]
steps:
- uses: actions/checkout@v4
- name: Use Node.js $
uses: actions/setup-node@v4
with:
node-version: $
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run Tests
run: npm test
build:
name: Build Application
runs-on: ubuntu-latest
needs: [test]
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: 'npm'
- name: Cache Build Artifacts
uses: actions/cache@v4
with:
path: .next/cache
key: $-next-$
- name: Build App
run: npm run build
- name: Upload Build Artifacts
uses: actions/upload-artifact@v4
with:
name: app-build
path: .next/
notify:
name: Slack Notification
runs-on: ubuntu-latest
if: failure()
steps:
- name: Send Slack Alert
run: |
curl -X POST -H 'Content-type: application/json' \
--data '{"text":"π¨ CI failed for $ on $"}' \
$
Use this to measure and track improvements as you optimize.
Job Name | Time Before | Time After | % Improvement | Optimization Applied |
---|---|---|---|---|
checkout |
15s | 15s | 0% | - |
lint |
30s | 20s | 33% | Fast-fail, fewer files |
test |
180s | 80s | 55% | Matrix, cache |
build |
150s | 100s | 33% | Dependency & layer cache |
notify |
10s | 10s | 0% | - |
Total Time | ~385s | ~225s | ~42% | Multiple improvements |
.github/
βββ workflows/
βββ ci.yaml
src/
βββ service-a/
βββ service-b/
βββ ...
package-lock.json
Dockerfile
Great! Hereβs a GitHub Actions deployment workflow (CD) that supports:
This example assumes youβre using Helm for deployment and GitHub Actions for automation. ArgoCD integration is included optionally.
cd.yaml
)# File: .github/workflows/cd.yaml
name: Deploy to Kubernetes
on:
workflow_dispatch:
inputs:
environment:
type: choice
description: 'Select environment'
required: true
options:
- staging
- production
version:
description: 'Docker Image Tag (e.g., v1.2.3)'
required: true
jobs:
deploy:
runs-on: ubuntu-latest
environment:
name: $
url: https://your-app.example.com
steps:
- name: Checkout Repo
uses: actions/checkout@v4
- name: Set Context & Variables
run: |
echo "ENVIRONMENT=$" >> $GITHUB_ENV
echo "VERSION=$" >> $GITHUB_ENV
- name: Set up kubectl
uses: azure/setup-kubectl@v4
with:
version: 'latest'
- name: Set up Helm
uses: azure/setup-helm@v4
with:
version: 'v3.13.0'
- name: Configure Kubeconfig
run: |
echo "$" | base64 -d > kubeconfig
export KUBECONFIG=$PWD/kubeconfig
- name: Helm Canary Deployment
run: |
helm upgrade --install my-app ./helm-chart \
--namespace $ENVIRONMENT \
--set image.tag=$VERSION \
--set deploymentStrategy=canary
approval:
needs: deploy
if: github.event.inputs.environment == 'production'
runs-on: ubuntu-latest
environment:
name: production
url: https://your-app.example.com
steps:
- name: Manual Approval
uses: hmarr/auto-approve-action@v3
with:
github-token: $
rollback:
needs: deploy
if: failure()
runs-on: ubuntu-latest
steps:
- name: Rollback via Helm
run: |
helm rollback my-app 1 --namespace $ENVIRONMENT
Your values.yaml
must support:
deploymentStrategy: "canary"
canary:
enabled: true
weight: 10
You can control traffic % using Istio, Linkerd, or nginx annotations if needed.
You can rollback using:
helm rollback
(as in the example above)argocd app rollback my-app --revision <old-revision>
Set the following in your GitHub repository secrets:
KUBECONFIG_BASE64
(your base64 encoded kubeconfig)GITHUB_TOKEN
ARGOCD_TOKEN
, ARGOCD_SERVER
for ArgoCD CLI integration
Let us know what you are working on?
We would help you to build a
fault tolerant, secure and scalable system over kubernetes.