Once you have the basics down, advanced patterns help you scale your CI/CD to larger teams and more complex projects. This lesson covers reusable workflows, custom actions, security scanning, and optimization techniques.
Reusable Workflows
Reusable workflows let you define a workflow once and call it from other workflows. This eliminates duplication across repositories.
Create a reusable workflow in .github/workflows/reusable-deploy.yml:
name: Reusable Deploy
on:
workflow_call:
inputs:
environment:
required: true
type: string
url:
required: true
type: string
secrets:
SSH_KEY:
required: true
HOST:
required: true
jobs:
deploy:
runs-on: ubuntu-latest
environment:
name: ${{ inputs.environment }}
url: ${{ inputs.url }}
steps:
- uses: actions/checkout@v4
- name: Deploy to ${{ inputs.environment }}
env:
SSH_KEY: ${{ secrets.SSH_KEY }}
HOST: ${{ secrets.HOST }}
run: |
echo "$SSH_KEY" > key.pem
chmod 600 key.pem
ssh -o StrictHostKeyChecking=no -i key.pem deploy@$HOST \
"cd /var/www/myapp && git pull && npm ci && npm run build && pm2 restart all"Call it from another workflow:
name: Deploy Pipeline
on:
push:
branches: [main]
jobs:
staging:
uses: ./.github/workflows/reusable-deploy.yml
with:
environment: staging
url: https://staging.myapp.com
secrets:
SSH_KEY: ${{ secrets.STAGING_SSH_KEY }}
HOST: ${{ secrets.STAGING_HOST }}
production:
needs: staging
uses: ./.github/workflows/reusable-deploy.yml
with:
environment: production
url: https://myapp.com
secrets:
SSH_KEY: ${{ secrets.PROD_SSH_KEY }}
HOST: ${{ secrets.PROD_HOST }}Composite Actions
Composite actions bundle multiple steps into a single reusable action. Create .github/actions/setup-project/action.yml:
name: Setup Project
description: Install dependencies and prepare the project
inputs:
node-version:
description: Node.js version
required: false
default: "20"
runs:
using: "composite"
steps:
- uses: pnpm/action-setup@v4
with:
version: 9
- uses: actions/setup-node@v4
with:
node-version: ${{ inputs.node-version }}
cache: "pnpm"
- name: Install dependencies
shell: bash
run: pnpm install --frozen-lockfileUse it in your workflows:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/setup-project
with:
node-version: "20"
- run: pnpm buildSecurity Scanning
Add security checks to your pipeline to catch vulnerabilities early:
name: Security
on:
push:
branches: [main]
pull_request:
schedule:
- cron: "0 8 * * 1" # Weekly scan on Monday
jobs:
dependency-audit:
name: Dependency Audit
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "20"
- run: npm audit --audit-level=high
codeql:
name: CodeQL Analysis
runs-on: ubuntu-latest
permissions:
security-events: write
steps:
- uses: actions/checkout@v4
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:
languages: javascript
- name: Perform CodeQL analysis
uses: github/codeql-action/analyze@v3
secret-scanning:
name: Secret Detection
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Detect secrets
uses: trufflesecurity/trufflehog@main
with:
extra_args: --only-verifiedMonorepo Support
For repositories with multiple packages, trigger workflows only for changed packages:
name: CI
on:
push:
branches: [main]
pull_request:
jobs:
detect-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@v3
id: changes
with:
filters: |
frontend:
- 'packages/frontend/**'
- 'packages/shared/**'
backend:
- 'packages/backend/**'
- 'packages/shared/**'
test-frontend:
needs: detect-changes
if: needs.detect-changes.outputs.frontend == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: cd packages/frontend && npm ci && npm test
test-backend:
needs: detect-changes
if: needs.detect-changes.outputs.backend == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: cd packages/backend && npm ci && npm testScheduled Maintenance Workflows
Automate routine tasks:
name: Maintenance
on:
schedule:
- cron: "0 9 * * 1" # Every Monday at 9 AM UTC
jobs:
update-dependencies:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "20"
- name: Update dependencies
run: |
npx npm-check-updates -u
npm install
- name: Create PR if changes exist
uses: peter-evans/create-pull-request@v6
with:
title: "chore: update dependencies"
commit-message: "chore: update dependencies"
branch: deps/weekly-update
body: |
Automated weekly dependency update.
Please review the changes and run tests before merging.
stale-issues:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v9
with:
days-before-stale: 60
days-before-close: 7
stale-issue-message: "This issue has been inactive for 60 days. It will be closed in 7 days unless there is activity."Workflow Debugging
When workflows fail and you need to investigate:
steps:
- name: Debug information
run: |
echo "Event: ${{ github.event_name }}"
echo "Ref: ${{ github.ref }}"
echo "SHA: ${{ github.sha }}"
echo "Actor: ${{ github.actor }}"
echo "Workflow: ${{ github.workflow }}"
echo "Run ID: ${{ github.run_id }}"
- name: List directory contents
run: ls -la
- name: Show environment
run: env | sortEnable debug logging by adding the secret ACTIONS_STEP_DEBUG with value true in your repository settings. This shows detailed output for every step.
Job Outputs and Dependencies
Pass data between jobs:
jobs:
version:
runs-on: ubuntu-latest
outputs:
app-version: ${{ steps.get-version.outputs.version }}
steps:
- uses: actions/checkout@v4
- id: get-version
run: echo "version=$(node -p 'require(\"./package.json\").version')" >> $GITHUB_OUTPUT
build:
needs: version
runs-on: ubuntu-latest
steps:
- name: Build version ${{ needs.version.outputs.app-version }}
run: echo "Building v${{ needs.version.outputs.app-version }}"Workflow Permissions
Follow the principle of least privilege:
permissions:
contents: read # Read repository code
packages: write # Push Docker images to GHCR
pull-requests: write # Comment on PRs
issues: write # Create issues
security-events: write # Upload CodeQL resultsSet minimal permissions at the workflow level and expand only where needed at the job level.
Best Practices Checklist
| Practice | Why |
|---|---|
Pin action versions (@v4, not @main) | Prevent supply chain attacks |
Use --frozen-lockfile for installs | Ensure reproducible builds |
Set timeout-minutes on all jobs | Prevent runaway jobs from burning minutes |
Use concurrency groups | Prevent redundant parallel runs |
| Store secrets in GitHub Secrets | Never hardcode credentials |
| Use environment protection rules | Require approval for production deploys |
| Run security scans weekly | Catch new vulnerabilities |
| Keep workflows DRY with reusable components | Easier maintenance |
Summary
You now have the advanced skills to build scalable CI/CD pipelines — reusable workflows for consistency, composite actions for shared setup logic, security scanning for safety, monorepo support for complex projects, and optimization techniques for speed. These patterns form the backbone of professional DevOps automation.