Agent skill
ci_cd_patterns
Install this agent skill to your Project
npx add-skill https://github.com/CleanExpo/ATO/tree/main/.agent/skills/ci_cd_patterns
SKILL.md
CI/CD Patterns
GitHub Actions workflow optimisation, caching strategies, and pipeline architecture for NodeJS-Starter-V1.
Metadata
| Field | Value |
|---|---|
| Skill ID | ci-cd-patterns |
| Category | Observability & DevOps |
| Complexity | Medium |
| Complements | docker-patterns, health-check, structured-logging |
| Version | 1.0.0 |
| Locale | en-AU |
Description
Codifies CI/CD patterns for NodeJS-Starter-V1: GitHub Actions workflow architecture with job dependency graphs, multi-layer caching (pnpm, uv, Turborepo, Docker), service containers for integration tests, security scanning pipelines, artifact management, monorepo path filtering, and optional-secret design that works without any external tokens.
When to Apply
Positive Triggers
- Creating or modifying GitHub Actions workflow files
- Adding new CI jobs (test, lint, build, deploy, scan)
- Optimising workflow run times or reducing redundant steps
- Configuring caching for package managers or build outputs
- Setting up deployment pipelines for backend or frontend
- Adding security scanning or dependency review steps
Negative Triggers
- Docker image building patterns (use
docker-patternsinstead) - Application-level health endpoints (use
health-checkinstead) - Local development scripts that do not run in CI
- Infrastructure provisioning (use
infrastructure-as-codewhen available)
Core Principles
The Three Laws of CI/CD
- Fast Feedback First: Cheap checks (lint, type-check) run before expensive ones (E2E, deploy). Fail fast to save developer time.
- Cache Everything, Trust Nothing: Cache dependency installs aggressively but always use
--frozen-lockfileto ensure reproducibility. - Zero Secrets Required: The pipeline must pass with zero configured secrets. Optional features (Snyk, deployment) activate only when their secrets are present.
Pattern 1: Workflow Architecture
Job Dependency Graph
The project's CI pipeline follows a diamond dependency pattern:
dependency-verification
│
┌────┴────┐
▼ ▼
backend frontend (parallel)
tests tests
│ │
└────┬────┘
│
┌────┴────┐
▼ ▼
build e2e-tests (parallel)
│
▼
accessibility
Project Reference: ci.yml:14-297 — 6 jobs with needs dependencies.
Job Dependency Rules
| Rule | Implementation | Rationale |
|---|---|---|
| Lint before test | needs: [dependency-verification] |
No point testing code that does not compile |
| Tests before build | needs: [backend-tests, frontend-tests] |
Build is expensive; only run if tests pass |
| Tests before E2E | needs: [backend-tests, frontend-tests] |
E2E is slowest; gate on unit tests first |
| Security parallel to CI | Separate workflow file | Security scans should not block feature PRs |
Workflow File Organisation
.github/workflows/
├── ci.yml # Core CI (tests, lint, build, E2E)
├── security.yml # Security scanning (Snyk, Trivy, npm audit)
├── agent-pr-checks.yml # Agent-generated PR validation
└── examples/
├── deploy-backend.yml.example # DigitalOcean deployment template
└── deploy-frontend.yml.example # Vercel deployment template
Rule: Separate concerns into distinct workflow files. CI, security, and deployment should not be in the same file. Use .example suffix for optional deployment workflows that require secrets.
Pattern 2: Caching Strategies
pnpm Cache (Frontend)
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: ${{ env.PNPM_VERSION }}
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: pnpm # Built-in pnpm cache support
Project Reference: ci.yml:23-31 — setup-node with cache: pnpm automatically caches the pnpm store.
uv Cache (Backend)
- name: Cache uv dependencies
uses: actions/cache@v4
with:
path: |
~/.cache/uv
apps/backend/.venv
key: ${{ runner.os }}-uv-${{ hashFiles('apps/backend/pyproject.toml') }}
restore-keys: |
${{ runner.os }}-uv-
Project Reference: ci.yml:87-95 — caches both the uv download cache and the virtualenv.
Turborepo Cache
Turborepo caches task outputs locally in .turbo/. For CI, enable remote caching:
- name: Build all packages
run: pnpm turbo run build
env:
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
Project Reference: turbo.json:5-8 — build outputs include .next/** (excluding cache) and dist/**.
Cache Key Design
| Cache | Key Pattern | Invalidation |
|---|---|---|
| pnpm | ${{ runner.os }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }} |
Lockfile changes |
| uv | ${{ runner.os }}-uv-${{ hashFiles('apps/backend/pyproject.toml') }} |
pyproject.toml changes |
| Turbo | Hash of inputs per task | Source file changes |
| Playwright | ${{ runner.os }}-playwright-${{ hashFiles('**/pnpm-lock.yaml') }} |
Lockfile changes |
Rule: Always use hashFiles() of the lockfile or dependency manifest as the cache key suffix. Use restore-keys with progressively shorter prefixes for partial cache hits.
Pattern 3: Service Containers
PostgreSQL for Integration Tests
services:
postgres:
image: postgres:15-alpine
env:
POSTGRES_DB: starter_db
POSTGRES_USER: starter_user
POSTGRES_PASSWORD: local_dev_password
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
Project Reference: ci.yml:60-73 — PostgreSQL service container in backend-tests job.
Service Container Rules
| Rule | Why |
|---|---|
Use -alpine images |
Faster pull times in CI |
| Always set healthcheck options | Prevents test step starting before DB is ready |
Match credentials to .env.example |
Consistency between local and CI environments |
Use localhost not service name |
Service containers map ports to the runner host |
Note: Service containers use localhost:PORT (not Docker network names) because GitHub Actions maps container ports directly to the runner.
Pattern 4: Security Scanning Pipeline
Multi-Layer Security Architecture
┌─────────────────────────────────────────────┐
│ Layer 1: Dependency Review (PR only) │
│ actions/dependency-review-action@v4 │
│ fail-on-severity: moderate │
│ deny-licenses: GPL-3.0, LGPL-3.0 │
├─────────────────────────────────────────────┤
│ Layer 2: NPM Audit (no token required) │
│ pnpm audit --audit-level=high │
├─────────────────────────────────────────────┤
│ Layer 3: Trivy FS Scan (no token required) │
│ aquasecurity/trivy-action — severity HIGH │
│ Uploads SARIF to GitHub Security tab │
├─────────────────────────────────────────────┤
│ Layer 4: Snyk (optional, needs SNYK_TOKEN) │
│ Frontend + Backend scans │
│ if: secrets.SNYK_TOKEN != '' │
└─────────────────────────────────────────────┘
Project Reference: security.yml:1-200 — all four layers implemented.
Optional Secret Pattern
snyk-frontend:
name: Snyk Frontend Security Scan (Optional)
runs-on: ubuntu-latest
if: ${{ secrets.SNYK_TOKEN != '' }}
steps:
# ...
Rule: Use if: ${{ secrets.SECRET_NAME != '' }} to conditionally run jobs that need tokens. This ensures the workflow passes when no secrets are configured (zero-barrier principle).
Pattern 5: Artifact Management
Upload Pattern
- name: Upload coverage report
if: always()
uses: actions/upload-artifact@v4
with:
name: backend-coverage
path: |
apps/backend/coverage.xml
apps/backend/htmlcov/
retention-days: 30
Project Reference: ci.yml:115-123 — coverage uploads in backend-tests job.
Artifact Naming Conventions
| Artefact | Name Pattern | Retention |
|---|---|---|
| Backend coverage | backend-coverage |
30 days |
| Frontend coverage | frontend-coverage |
30 days |
| Test results | {scope}-test-results |
30 days |
| Playwright report | playwright-report |
30 days |
| Security reports | {scanner}-report |
30 days |
| Dependency verification | dependency-verification-report |
30 days |
Rule: Always use if: always() on artifact upload steps. Test results and coverage reports are most valuable when tests fail, not when they pass.
Pattern 6: Monorepo Path Filtering
Deploy Only Changed Services
on:
push:
branches: [main]
paths:
- "apps/backend/**"
Project Reference: deploy-backend.yml.example:5-7 — only triggers on backend changes.
Path Filter Patterns
| Service | Path Filter | Rationale |
|---|---|---|
| Backend deploy | apps/backend/** |
Only deploy backend when backend changes |
| Frontend deploy | apps/web/**, packages/** |
Include shared packages |
| Full CI | No filter (all pushes) | Tests should run on every change |
| Security | No filter + weekly schedule | Security applies to everything |
Rule: Deployment workflows should use paths filters to avoid unnecessary deployments. CI and security workflows should run on all changes.
Pattern 7: Agent PR Validation
Agent-Specific Quality Gates
The project has a dedicated workflow for AI-generated PRs:
on:
pull_request:
branches: [main, develop]
jobs:
detect-agent-pr:
steps:
- name: Check if agent PR
run: |
if [[ "${{ github.head_ref }}" == feature/agent-* ]]; then
echo "is_agent=true" >> $GITHUB_OUTPUT
fi
Project Reference: agent-pr-checks.yml:1-174 — validates agent metadata, runs quality checks, scans for secrets/debug code, posts PR status comment.
Agent PR Checks
| Check | Purpose | Blocking |
|---|---|---|
| Agent metadata (ID, Task, Verifier) | Audit trail for agent-generated code | Yes |
| Type check + Lint + Test + Build | Standard quality gates | Yes |
| Secret scan (hardcoded keys/passwords) | Prevent credential leaks | Yes |
| Debug code scan (console.log, print) | Clean production code | Warning |
Pattern 8: Environment Variable Management
Centralised Version Pinning
env:
NODE_VERSION: '20'
PNPM_VERSION: 9
PYTHON_VERSION: '3.12'
Project Reference: ci.yml:9-13 — version matrix defined at workflow level.
Rule: Define tool versions once at the workflow env level, then reference with ${{ env.TOOL_VERSION }} in all jobs. This ensures consistency and makes upgrades a single-line change.
Secret Tiers
| Tier | Examples | Required | Used By |
|---|---|---|---|
| None | — | Core CI, tests, lint | ci.yml |
| Optional | SNYK_TOKEN, CODECOV_TOKEN |
Enhanced scanning | security.yml |
| Deploy | VERCEL_TOKEN, DO_API_TOKEN |
Production deployment | deploy-*.yml |
Project Reference: .github/SECRETS.md:1-60 — full secrets documentation with setup instructions.
Pattern 9: Verification Scripts
Reusable Shell Functions
# scripts/dependency-checks.sh — sourced by CI and verify.sh
source scripts/dependency-checks.sh
if ! check_lockfile_integrity; then
echo "::error::Lockfile integrity check failed"
exit 1
fi
Project Reference: scripts/dependency-checks.sh:10-50 — lockfile integrity checks. ci.yml:34-39 — used in dependency-verification job.
Rule: Extract verification logic into reusable shell scripts in scripts/. Source them in both CI workflows and local verification commands (pnpm run verify). This ensures local and CI validation are identical.
Anti-Patterns
| Pattern | Problem | Correct Approach |
|---|---|---|
| All jobs sequential | Pipeline takes 15+ min | Parallelise independent jobs |
No --frozen-lockfile |
CI installs different versions than local | Always use --frozen-lockfile / --frozen |
Caching node_modules/ directly |
Fragile, breaks on OS/Node version changes | Cache pnpm store via setup-node |
continue-on-error: true on tests |
Failing tests are silently ignored | Use only on optional scans (Snyk) |
| Secrets required for CI to pass | Contributors without secrets cannot verify PRs | Use if: secrets.X != '' for optional jobs |
| Same workflow for CI and deploy | Deploy failures block CI feedback | Separate workflow files |
No if: always() on artifact upload |
Artefacts lost when tests fail | Always upload regardless of job status |
| Hardcoded tool versions in each job | Version drift between jobs | Centralise in workflow-level env |
| Running E2E before unit tests | Slow feedback for simple failures | Gate E2E on unit test success |
Checklist
Before merging CI/CD changes:
- Jobs parallelised where independent (tests, scans)
- Expensive jobs gated behind cheap checks (
needs) -
--frozen-lockfileused for all package installs - Package manager caches configured (pnpm, uv)
- Service containers have healthcheck options
- Artefact uploads use
if: always()andretention-days - Optional features use
if: secrets.X != ''guard - Tool versions centralised in workflow
envblock - Deployment workflows use
pathsfilter - Security scans run on schedule (weekly) in addition to push/PR
Response Format
When applying this skill, structure implementation as:
### CI/CD Implementation
**Workflow**: [ci / security / deploy / custom]
**Trigger**: [push / pull_request / schedule / workflow_dispatch]
**Jobs**: [job dependency graph]
**Caching**: [pnpm / uv / Turbo / Docker layers]
**Secrets Required**: [none / optional / required]
**Path Filter**: [all / specific paths]
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
ato-rate-scraping
Automated scraping and extraction of current Australian tax rates from ATO.gov.au and related government sources
rate_limiter
payg-instalment-optimization
PAYG instalment strategy analysis — amount vs rate method, variation penalty risk, GDP adjustment
idea-queue-capture
Instantly capture user ideas to work queue with intelligent grouping
simple-report-export
Simple report generation without Google Cloud - uses Gmail App Password or local file export (PDF, Excel, Word-compatible formats)
cashflow-forecasting
Project future tax obligations and cash flow requirements based on historical transaction patterns
Didn't find tool you were looking for?