CI/CD pipeline design and implementation for GitHub Actions, Azure DevOps, and general pipeline architecture. Use when creating build pipelines, deployment workflows, quality gates, environment promotion strategies, or automating release processes.
Instal·la
npx skillscat add saajunaid/caddis-plugin/ci-cd-pipeline Instal·la des del registre de SkillsCat.
CI/CD Pipeline Skill
Automate the path from commit to production. Every merge should be deployable.
1. When to Apply This Skill
Trigger conditions:
- Setting up CI/CD for a new project
- "Create a build and deploy pipeline"
- Adding quality gates to an existing pipeline
- Designing environment promotion strategy
- Troubleshooting pipeline failures or slow builds
2. Pipeline Architecture
Standard Flow
Commit → Lint → Test → Build → [Quality Gate] → Deploy Staging → [Approval] → Deploy ProductionPipeline Stages
| Stage | Purpose | Fail = Block? |
|---|---|---|
| Lint | Code style, formatting | Yes |
| Type Check | Static type errors | Yes |
| Unit Test | Logic correctness | Yes |
| Integration Test | Service interactions | Yes |
| Build | Compile, bundle, containerise | Yes |
| Security Scan | Vulnerabilities, secrets | Yes (critical), Warn (medium) |
| Deploy Staging | Validate in staging env | Yes |
| Smoke Test | Basic health check post-deploy | Yes |
| Deploy Production | Ship to users | Manual approval required |
3. GitHub Actions — Python Project
name: CI
context: fork
on:
push:
branches: [main]
pull_request:
branches: [main]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
cache: pip
- run: pip install ruff
- run: ruff check .
- run: ruff format --check .
test:
runs-on: ubuntu-latest
needs: lint
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
cache: pip
- run: pip install -e ".[test]"
- run: pytest --cov=src --cov-report=xml -q
- uses: codecov/codecov-action@v4
with:
file: coverage.xml
build:
runs-on: ubuntu-latest
needs: test
steps:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- uses: docker/build-push-action@v5
with:
context: .
push: false
tags: app:${{ github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max4. GitHub Actions — Node.js/TypeScript Project
name: CI
context: fork
on:
push:
branches: [main]
pull_request:
branches: [main]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
quality:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm lint
- run: pnpm typecheck
- run: pnpm test -- --coverage
- run: pnpm build5. Quality Gates
Define pass/fail criteria for each stage.
# quality-gates.yml (reference document)
gates:
lint:
ruff_errors: 0
format_diff: 0
test:
min_coverage: 80
max_test_duration_seconds: 120
allowed_failures: 0
security:
critical_vulnerabilities: 0
high_vulnerabilities: 0
build:
max_image_size_mb: 500
max_build_time_minutes: 10
deploy:
smoke_test_pass: true
health_check_pass: true
rollback_on_error_rate: 5 # percent6. Environment Promotion Strategy
Feature Branch → PR Review → Main → Staging → Production
│ │ │
│ │ └── Manual approval
│ └── Auto-deploy on main merge
└── CI runs on every PR| Environment | Deploy Trigger | Approval | Data |
|---|---|---|---|
| Dev | Every push to branch | None | Synthetic/mock |
| Staging | Merge to main | Automatic | Production-like (anonymised) |
| Production | Tag or manual | Required (2 reviewers) | Real |
7. Secrets Management
# ✅ GOOD: Use GitHub Actions secrets
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }}
API_KEY: ${{ secrets.API_KEY }}
# ✅ GOOD: Use OIDC for cloud auth (no long-lived credentials)
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
aws-region: eu-west-1
# ❌ BAD: Hardcoded secrets in workflow
env:
API_KEY: "sk-abc123" # NEVER DO THISRules:
- Never commit secrets to the repository
- Use environment-scoped secrets (staging secrets ≠ production secrets)
- Rotate secrets on a schedule (90 days max)
- Use OIDC federation over long-lived credentials where possible
8. Caching Strategy
# Python pip cache
- uses: actions/setup-python@v5
with:
cache: pip
# Node.js pnpm cache
- uses: actions/setup-node@v4
with:
cache: pnpm
# Docker layer cache
- uses: docker/build-push-action@v5
with:
cache-from: type=gha
cache-to: type=gha,mode=max
# Custom cache (e.g., test fixtures)
- uses: actions/cache@v4
with:
path: .test-cache
key: test-fixtures-${{ hashFiles('tests/fixtures/**') }}9. Pipeline Performance
Keep CI fast. Developers won't wait for slow pipelines.
| Target | Goal |
|---|---|
| Lint + type check | < 2 minutes |
| Unit tests | < 5 minutes |
| Full pipeline (PR) | < 15 minutes |
| Deploy to staging | < 5 minutes |
Optimisation techniques:
- Run lint and test in parallel (separate jobs)
- Cache dependencies aggressively
- Use
concurrencyto cancel outdated runs - Split slow test suites with
pytest -x --dist=loadgroup - Use Docker layer caching for builds
10. Rollback Strategy
| Approach | Speed | Complexity |
|---|---|---|
| Revert commit | Fast (minutes) | Low — git revert + re-deploy |
| Blue-green deploy | Instant (seconds) | Medium — swap traffic target |
| Canary rollback | Instant | High — requires traffic splitting |
| Database rollback | Slow, risky | High — only if migration is reversible |
Rule: Always prefer forward-fix (deploy a fix) over rollback. Rollback is the escape hatch, not the default.
11. Anti-Patterns
| Anti-Pattern | Problem | Fix |
|---|---|---|
| No CI on PRs | Bugs merge to main | Run full pipeline on every PR |
| Deploy from local machine | "Works on my machine" | Deploy only from CI/CD |
| Single mega-job | Slow, hard to debug | Split into parallel stages |
| No caching | 10-min installs every run | Cache deps, Docker layers |
| Manual production deploy | Human error, inconsistent | Automated with approval gate |
| Secrets in code | Compromised on push | Use secrets manager + OIDC |
| No rollback plan | Broken deploy = downtime | Blue-green or revert strategy |
| Build passes but output is wrong | Config error baked into assets | Post-build output validation gate (§12) |
12. Frontend Build Output Validation
A successful build (exit code 0) does not guarantee correct output. Configuration errors — especially wrong asset path prefixes — produce valid bundles that break at runtime. Add a post-build validation step in CI:
12.1 Subpath Base-Path Check
When a frontend is deployed under a URL subpath (e.g. /my-app/), the built index.html must reference assets with that prefix. Vite's base config controls this, and it can be accidentally changed.
# GitHub Actions / Gitea Actions example
jobs:
frontend-checks:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
- run: npm ci
working-directory: frontend
# Build with production mode to activate conditional base path
- run: npx vite build --mode production
working-directory: frontend
- name: Validate production base path
working-directory: frontend
run: |
if ! grep -q '/my-app/assets/' dist/index.html; then
echo "FAIL: Built index.html does not reference /my-app/assets/"
echo "Check vite.config.ts base setting"
exit 1
fi
echo 'Base-path guard passed'Key points:
- Build with
--mode production— without it, mode-conditionalbasestays as"/"and the guard always fails - This catches the exact scenario where someone changes
base: mode === "production" ? "/my-app/" : "/"tobase: "/" - The same check should also run in the deploy script as a belt-and-suspenders guard (abort before service restart)
12.2 General Output Validation Patterns
| Check | Command | When to use |
|---|---|---|
| Subpath prefix | grep -q '/{subpath}/assets/' dist/index.html |
Subpath deployments |
| Non-empty dist | test -d dist/assets && ls dist/assets/*.js |
Always |
| Bundle size budget | du -sh dist/ | awk '{print $1}' |
When size regressions matter |
| No source maps in prod | ! ls dist/assets/*.map 2>/dev/null |
Production builds |