Beyond Basic Workflows
Most teams start with a simple GitHub Actions workflow that runs tests on push. But production-grade CI/CD requires much more: matrix builds across runtime versions, environment protection rules, secrets management, caching strategies, artifact handling, and automated rollback triggers.
This guide covers advanced patterns that we implement when setting up DevOps pipelines for engineering teams shipping to production multiple times per day.
Pipeline Architecture
A robust CI/CD pipeline has distinct stages with clear gates between them:
Build Stage: Compile, lint, and run unit tests. Fail fast on code quality issues.
Test Stage: Integration tests, E2E tests, and security scans run in parallel. Each test suite runs in its own job for isolation and parallelism.
Deploy Stage: Progressive deployment through staging, canary, and production environments with manual approval gates.
Advanced Workflow Configuration
name: Production Pipeline
on:
push:
branches: [main]
pull_request:
branches: [main]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [18, 20, 22]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
- run: npm ci
- run: npm run lint
- run: npm run build
- uses: actions/upload-artifact@v4
if: matrix.node-version == 20
with:
name: build-output
path: dist/
retention-days: 5
test:
needs: build
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
test-suite: [unit, integration, e2e]
services:
postgres:
image: postgres:16
env:
POSTGRES_DB: test_db
POSTGRES_PASSWORD: test_pass
ports: ['5432:5432']
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'npm'
- run: npm ci
- run: npm run test:${{ matrix.test-suite }}
env:
DATABASE_URL: postgres://postgres:test_pass@localhost:5432/test_db
Key Design Decisions
The concurrency block cancels in-progress runs when new commits arrive on the same branch — essential for busy repositories where developers push frequently.
Setting fail-fast: false on the test matrix ensures all test suites complete even if one fails. This gives developers complete failure information in a single run rather than requiring multiple push-and-wait cycles.
Environment Protection Rules
GitHub Environments provide approval gates and deployment tracking:
deploy-staging:
needs: [test]
runs-on: ubuntu-latest
environment:
name: staging
url: https://staging.example.com
steps:
- uses: actions/download-artifact@v4
with:
name: build-output
path: dist/
- name: Deploy to staging
run: |
aws s3 sync dist/ s3://staging-bucket/ --delete
aws cloudfront create-invalidation \
--distribution-id ${{ secrets.CF_DIST_ID }} \
--paths "/*"
deploy-production:
needs: [deploy-staging]
runs-on: ubuntu-latest
environment:
name: production
url: https://example.com
steps:
- uses: actions/download-artifact@v4
with:
name: build-output
path: dist/
- name: Deploy to production
run: |
aws s3 sync dist/ s3://production-bucket/ --delete
aws cloudfront create-invalidation \
--distribution-id ${{ secrets.PROD_CF_DIST_ID }} \
--paths "/*"
Configure the production environment in GitHub to require manual approval from team leads. The workflow pauses at the deploy-production job until an authorized reviewer approves.
Caching for Speed
Aggressive caching cuts build times by 60-80 percent:
- name: Cache dependencies
uses: actions/cache@v4
with:
path: |
~/.npm
node_modules
.next/cache
key: deps-${{ runner.os }}-${{ hashFiles('package-lock.json') }}
restore-keys: |
deps-${{ runner.os }}-
Automated Rollback
Implement post-deployment health checks that trigger automatic rollback:
- name: Smoke test
id: smoke
continue-on-error: true
run: |
for i in {1..5}; do
STATUS=$(curl -s -o /dev/null -w "%{http_code}" https://example.com/healthz)
if [ "$STATUS" != "200" ]; then
echo "Health check failed with status $STATUS"
exit 1
fi
sleep 5
done
- name: Rollback on failure
if: steps.smoke.outcome == 'failure'
run: |
aws s3 sync s3://production-backup/ s3://production-bucket/ --delete
aws cloudfront create-invalidation \
--distribution-id ${{ secrets.PROD_CF_DIST_ID }} \
--paths "/*"
echo "::error::Deployment rolled back due to failed health checks"
exit 1
Security Scanning
Add SAST and dependency scanning as required checks:
security:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
scan-type: 'fs'
severity: 'CRITICAL,HIGH'
exit-code: '1'
Best Practices
- Keep workflows DRY using reusable workflows and composite actions
- Pin action versions to specific SHA hashes, not tags, for supply chain security
- Use OIDC for AWS authentication instead of long-lived access keys
- Store deployment metadata (commit SHA, timestamp, actor) for audit trails
- Set up monitoring and alerting to catch issues that automated tests miss
Implementing these patterns transforms CI/CD from a simple automation layer into a reliable software delivery system.
Need help with this?
Our team handles this kind of work daily. Let us take care of your infrastructure.
Related Articles
The Ultimate Guide to Linux Server Management in 2025
A comprehensive guide to modern Linux server management covering automation, containerization, cloud integration, AI-driven operations, security best practices, and essential tooling for 2025.
Server & DevOpsFixing "421 Misdirected Request" for Plesk Sites on Ubuntu 22.04 After Apache Update
Resolve the 421 Misdirected Request error affecting all HTTPS sites on Plesk for Ubuntu 22.04 after an Apache update, caused by changed SNI requirements in the nginx-to-Apache proxy chain.
Server & DevOpsHow to Set Up GlusterFS on Ubuntu
A complete guide to setting up a distributed, replicated GlusterFS filesystem across multiple Ubuntu 22.04 nodes, including installation, volume creation, client mounting, maintenance, and troubleshooting.