writing-skills
writing-skills
Use when creating new skills, editing existing skills, or verifying skills work before deployment
cicd-pipelineUse this skill when the user says 'CI/CD', 'GitHub Actions', 'GitLab CI', 'pipeline', 'deployment pipeline', 'automated testing pipeline', 'workflow yaml', 'build pipeline', 'deploy workflow', 'continuous integration', 'continuous delivery'. Covers: pipeline stages (lint → test → build → security scan → deploy), caching, parallel execution, deployment strat…
Independent GitHub snapshot · Automated classification · Original documentation governs
Design and implement CI/CD pipelines with proper stages, dependency caching, parallel execution, security scanning, deployment strategies, and rollback procedures.
| Feature | GitHub Actions | GitLab CI | Jenkins | CircleCI |
|---|---|---|---|---|
| Hosted runners | Linux, macOS, Windows | Linux, macOS, Windows | N/A (self-hosted) | Linux, macOS, Windows |
| Self-hosted runners | Yes | Yes | Native | Yes |
| Reusable configs | Composite actions + reusable workflows | Include templates + CI components | Shared libraries | Orbs |
| Container support | Native (service containers) | Native (services) | Docker plugin | Native |
| Secret management | GitHub Secrets | CI/CD Variables | Credentials plugin | Project env vars |
| Parallelism | Matrix strategy | Parallel keyword | Parallel stages | Parallelism × resource class |
| Cache | actions/cache | cache: keyword | Plugin | Store/cache |
| Artifacts | Built-in | Built-in | Built-in | Built-in |
| Pricing | Free: 2000 min/mo | Free: 400 min/mo | Free | Free: 6000 min/mo |
| Best for | Open source, GitHub-native | Monorepo, GitLab-native | Complex enterprise | Performance-focused |
| Stage | When to Include | Estimated Time | Fail Fast |
|---|---|---|---|
| Lint | Always (fast feedback) | <2 min | Yes (fail early) |
| Unit tests | Always | <10 min | Yes |
| Build | Always | 1-30 min | Yes |
| Integration tests | Code changes affect DB/external | 5-30 min | After build |
| Security scan | Production branches | 2-15 min | Yes (high vulns) |
| Docker build | Containerized apps | 2-10 min | Yes |
| Deploy staging | PR to main/develop | 1-10 min | No (non-blocking) |
| E2E tests | Before production deployment | 10-30 min | On staging |
| Deploy production | Main branch merge | 1-15 min | With approval gate |
| Smoke tests | Every production deploy | 2-5 min | Yes (rollback) |
| Manager | Cache Key | Cache Path | Restore Behavior |
|---|---|---|---|
| npm | npm-cache-${{ hashFiles('package-lock.json') }} | ~/.npm | Restore + install |
| pip | pip-cache-${{ hashFiles('requirements.txt') }} | ~/.cache/pip | Restore only |
| Go | go-mod-cache-${{ hashFiles('go.sum') }} | ~/go/pkg/mod | Restore + download |
| Cargo | cargo-cache-${{ hashFiles('Cargo.lock') }} | ~/.cargo/registry | Restore + build |
| Maven | maven-cache-${{ hashFiles('pom.xml') }} | ~/.m2/repository | Restore + install |
| Gradle | gradle-cache-${{ hashFiles('*.gradle*') }} | ~/.gradle/caches | Restore + build |
| Yarn | yarn-cache-${{ hashFiles('yarn.lock') }} | ~/.cache/yarn | Restore + install |
| Strategy | Downtime | Risk | Rollback Speed | Complexity | Traffic Shift |
|---|---|---|---|---|---|
| Recreate | Yes (full) | High | N/A | Low | Instant |
| Rolling | None | Medium | Medium | Low | Gradual (batch) |
| Blue/Green | None | Low | Instant | Medium | DNS/LB switch |
| Canary | None | Very Low | Fast | High | %-based routing |
| A/B Testing | None | Very Low | Fast | High | User segment-based |
| Scan Type | Tools | Stage | Speed |
|---|---|---|---|
| SAST (Static Analysis) | SonarQube, Semgrep, CodeQL, Snyk Code | After checkout | 5-15 min |
| DAST (Dynamic) | OWASP ZAP, Burp Suite | After deploy to staging | 10-30 min |
| Dependency scan | OWASP DC, Snyk, Trivy, npm audit | After install | 2-5 min |
| Container scan | Trivy, Clair, Snyk Container, Grype | After docker build | 2-10 min |
| IaC scan | Checkov, tfsec, KICS, Terrascan | After infrastructure code | 1-3 min |
| Secret scan | Gitleaks, TruffleHog, GitGuardian | On every commit | 1-3 min |
Pipeline stages: lint → test → build → security scan → deploy staging → deploy prod. Cache dependencies with content-hash keys. Use environment secrets. Implement blue-green or canary for prod.
name: CI/CD Pipeline
on:
push: { branches: [main] }
pull_request: { branches: [main] }
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
NODE_VERSION: "22"
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: "${{ env.NODE_VERSION }}", cache: 'npm' }
- run: npm ci
- run: npm run lint
test:
needs: lint
runs-on: ubuntu-latest
services:
postgres:
image: postgres:16-alpine
env: { POSTGRES_PASSWORD: testpass }
options: >-
--health-cmd pg_isready --health-interval 10s
strategy:
matrix:
node-version: [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 test
- run: npm run test:integration
env: { DATABASE_URL: postgres://postgres:testpass@localhost:5432/postgres }
security:
needs: lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npm audit --audit-level=high
- uses: aquasecurity/trivy-action@master
with:
scan-type: 'fs'
scan-ref: '.'
format: 'sarif'
output: 'trivy-results.sarif'
severity: 'CRITICAL,HIGH'
build:
needs: test
runs-on: ubuntu-latest
outputs:
image-tag: ${{ steps.build.outputs.image-tag }}
steps:
- uses: actions/checkout@v4
- run: npm ci && npm run build
- uses: docker/setup-buildx-action@v3
- uses: docker/login-action@v3
with: { registry: ghcr.io, username: ${{ github.actor }},
password: ${{ secrets.GITHUB_TOKEN }} }
- id: build
uses: docker/build-push-action@v5
with:
push: true
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
deploy-staging:
needs: [build, security]
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
environment: staging
steps:
- run: echo "Deploying to staging..."
- run: echo "Tag: ${{ needs.build.outputs.image-tag }}"
deploy-production:
needs: deploy-staging
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
environment: production
concurrency: production-deploy
steps:
- uses: actions/checkout@v4
- run: |
echo "Deploying to production (canary 10% → 50% → 100%)"
echo "Health check: https://app.example.com/health"
# .gitlab-ci.yml
stages:
- lint
- test
- build
- security
- deploy
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- node_modules/
- .npm/
variables:
DOCKER_IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA
NODE_VERSION: "22"
lint:
stage: lint
image: node:${NODE_VERSION}
script:
- npm ci
- npm run lint
test:
stage: test
image: node:${NODE_VERSION}
services:
- postgres:16-alpine
variables:
DATABASE_URL: postgres://postgres:testpass@postgres:5432/postgres
script:
- npm ci
- npm run test
- npm run test:integration
security:
stage: security
image: aquasec/trivy:latest
script:
- trivy fs --severity CRITICAL,HIGH --exit-code 1 .
build:
stage: build
image: docker:latest
services:
- docker:dind
script:
- docker build -t $DOCKER_IMAGE .
- docker push $DOCKER_IMAGE
.deploy:
stage: deploy
image: alpine
script:
- echo "Deploying $DOCKER_IMAGE to $CI_ENVIRONMENT_NAME"
deploy-staging:
extends: .deploy
environment:
name: staging
url: https://staging.example.com
rules:
- if: $CI_COMMIT_BRANCH == "main"
deploy-production:
extends: .deploy
environment:
name: production
url: https://app.example.com
rules:
- if: $CI_COMMIT_BRANCH == "main"
when: manual
needs: [deploy-staging]
# kubernetes/canary-deploy.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp-stable
labels: { app: myapp, track: stable }
spec:
replicas: 9
selector:
matchLabels: { app: myapp, track: stable }
template:
metadata:
labels: { app: myapp, track: stable }
spec:
containers:
- name: app
image: myapp:1.0.0
ports: [{ containerPort: 8080 }]
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp-canary
labels: { app: myapp, track: canary }
spec:
replicas: 1
selector:
matchLabels: { app: myapp, track: canary }
template:
metadata:
labels: { app: myapp, track: canary }
spec:
containers:
- name: app
image: myapp:1.1.0
ports: [{ containerPort: 8080 }]
---
apiVersion: v1
kind: Service
metadata:
name: myapp-service
spec:
selector: { app: myapp }
ports: [{ port: 80, targetPort: 8080 }]
# parallel matrix build
test:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest]
node: [18, 20, 22]
experimental: [false]
include:
- os: ubuntu-latest
node: 23
experimental: true
max-parallel: 6
continue-on-error: ${{ matrix.experimental }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: "${{ matrix.node }}" }
- run: npm ci && npm test
# rollback/rollback.sh
#!/bin/bash
set -euo pipefail
ENVIRONMENT="${1:-production}"
IMAGE_TAG="${2:-rollback-target}"
echo "=== Initiating rollback for $ENVIRONMENT to $IMAGE_TAG ==="
# 1. Stop further deployment
echo "Locking deployment pipeline..."
export DEPLOY_LOCK=true
# 2. Rollback application
echo "Redeploying previous version..."
kubectl set image deployment/myapp \
app=myapp:"$IMAGE_TAG" \
-n "$ENVIRONMENT"
# 3. Rollback database (if needed)
echo "Verifying database schema..."
# Run migration rollback if schema changed
# ./rollback-db.sh
# 4. Verify health
echo "Running health checks..."
for i in {1..30}; do
HEALTH=$(curl -sf -o /dev/null -w "%{http_code}" "https://$ENVIRONMENT.example.com/health")
if [ "$HEALTH" = "200" ]; then
echo "Health check passed"
break
fi
echo "Waiting for health... attempt $i"
sleep 10
done
# 5. Notify team
echo "Rollback to $IMAGE_TAG completed"
Single giant job with all steps sequentially. Split into parallel stages that can fail independently.
Not caching dependencies, rebuilding from scratch every time. Add dependency caching to reduce pipeline time by 50-80%.
Storing API keys, passwords in YAML files. Use CI/CD secrets/environment variables with access control.
Automatic production deployment without approval gates. Add manual approval for production deployments.
No rollback plan documented. Every deployment must have a tested rollback procedure.
Pipeline runs for 30+ min before failing at end. Fail fast: lint first, then build, then test.
Next: kubernetes-patterns — K8s deployment for pipeline output.
stages:
- lint
- test
- build
- deploy
lint-job:
stage: lint
script:
- npm ci
- npm run lint
cache:
key: $CI_COMMIT_REF_SLUG
paths:
- node_modules/
test-job:
stage: test
script:
- npm run test:unit
- npm run test:integration
artifacts:
reports:
junit: junit.xml
build-job:
stage: build
script:
- docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA .
- docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
only:
- main
- tags
deploy-staging:
stage: deploy
script:
- kubectl set image deployment/app app=$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
environment: staging
only:
- main
#!/usr/bin/env bash
set -euo pipefail
promote_to_prod() {
local tag=$1
local env=$2
echo "Promoting $tag to $env"
# Run smoke tests against staging
if ! curl -sf "https://staging.example.com/health" | grep -q "ok"; then
echo "Staging health check failed — aborting promotion"
exit 1
fi
# Deploy to production
kubectl set image deployment/app \
"app=${CI_REGISTRY_IMAGE}:${tag}" \
--namespace production
# Monitor rollout
kubectl rollout status deployment/app \
--namespace production --timeout=5m
}
promote_to_prod "$@"
latest tag for Docker images — always pin to semantic version or commit SHA