--- name: ci-cd-patterns description: "CI/CD: GitHub Actions, GitLab CI, Jenkins, caching, blue-green, canary. Triggers: CI, CD, pipeline, GitHub Actions, workflow YAML, release, canary, rollout." effort: medium user-invocable: false allowed-tools: Read --- # CI/CD Patterns ## GitHub Actions ### Standard Pipeline ```yaml name: CI on: push: branches: [main] pull_request: branches: [main] jobs: lint: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - uses: actions/setup-node@v6 with: node-version: 20 cache: "npm" - run: npm ci - run: npm run lint - run: npm run typecheck test: runs-on: ubuntu-latest needs: lint strategy: matrix: node-version: [18, 20, 22] steps: - uses: actions/checkout@v6 - uses: actions/setup-node@v6 with: node-version: ${{ matrix.node-version }} cache: "npm" - run: npm ci - run: npm test -- --coverage - uses: actions/upload-artifact@v4 with: name: coverage-${{ matrix.node-version }} path: coverage/ build: runs-on: ubuntu-latest needs: test steps: - uses: actions/checkout@v6 - uses: actions/setup-node@v6 with: node-version: 20 cache: "npm" - run: npm ci - run: npm run build ``` ### Python CI ```yaml name: Python CI on: [push, pull_request] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - uses: actions/setup-python@v5 with: python-version: "3.12" cache: "pip" - run: pip install -e ".[dev]" - run: ruff check . - run: mypy --strict src/ - run: pytest --cov=src --cov-report=xml ``` ### Docker Build & Push ```yaml build-docker: runs-on: ubuntu-latest needs: test steps: - uses: actions/checkout@v6 - uses: docker/setup-buildx-action@v3 - uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - uses: docker/build-push-action@v5 with: context: . push: ${{ github.ref == 'refs/heads/main' }} tags: ghcr.io/${{ github.repository }}:${{ github.sha }} cache-from: type=gha cache-to: type=gha,mode=max ``` ## GitLab CI ```yaml stages: - lint - test - build - deploy lint: stage: lint image: node:20 cache: key: $CI_COMMIT_REF_SLUG paths: [node_modules/] script: - npm ci - npm run lint test: stage: test image: node:20 services: - postgres:16 variables: DATABASE_URL: "postgresql://postgres:postgres@postgres/test" script: - npm ci - npm test build: stage: build image: docker:24 services: - docker:24-dind script: - docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA . - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA only: - main ``` ## Docker Multi-stage Builds ### Node.js ```dockerfile # Stage 1: Dependencies FROM node:20-alpine AS deps WORKDIR /app COPY package.json package-lock.json ./ RUN npm ci --production # Stage 2: Build FROM node:20-alpine AS build WORKDIR /app COPY package.json package-lock.json ./ RUN npm ci COPY . . RUN npm run build # Stage 3: Production FROM node:20-alpine AS production WORKDIR /app ENV NODE_ENV=production COPY --from=deps /app/node_modules ./node_modules COPY --from=build /app/dist ./dist USER node EXPOSE 3000 CMD ["node", "dist/index.js"] ``` ### Python ```dockerfile FROM python:3.12-slim AS builder WORKDIR /app COPY pyproject.toml . RUN pip install --no-cache-dir --target=/deps . FROM python:3.12-slim WORKDIR /app COPY --from=builder /deps /usr/local/lib/python3.12/site-packages COPY src/ ./src/ USER nobody CMD ["python", "-m", "src.main"] ``` ## Caching Strategies ### npm/pnpm ```yaml - uses: actions/cache@v4 with: path: ~/.npm key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }} restore-keys: ${{ runner.os }}-npm- ``` ### pip ```yaml - uses: actions/cache@v4 with: path: ~/.cache/pip key: ${{ runner.os }}-pip-${{ hashFiles('**/pyproject.toml') }} ``` ### Docker Layer Caching ```yaml - uses: docker/build-push-action@v5 with: cache-from: type=gha cache-to: type=gha,mode=max ``` ## Kubernetes Deployment ### Rolling Update ```yaml apiVersion: apps/v1 kind: Deployment metadata: name: api spec: replicas: 3 strategy: type: RollingUpdate rollingUpdate: maxSurge: 1 maxUnavailable: 0 template: spec: containers: - name: api image: ghcr.io/org/api:latest readinessProbe: httpGet: path: /ready port: 3000 initialDelaySeconds: 5 livenessProbe: httpGet: path: /live port: 3000 initialDelaySeconds: 10 ``` ## Secret Management ```yaml # GitHub Actions - use secrets env: DATABASE_URL: ${{ secrets.DATABASE_URL }} API_KEY: ${{ secrets.API_KEY }} # Never hardcode secrets in pipelines # Use OIDC for cloud provider auth (no long-lived credentials) - uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: arn:aws:iam::123456789:role/deploy aws-region: us-east-1 ``` ## Release Automation ### Semantic Release ```json { "branches": ["main"], "plugins": [ "@semantic-release/commit-analyzer", "@semantic-release/release-notes-generator", "@semantic-release/changelog", "@semantic-release/npm", "@semantic-release/github", "@semantic-release/git" ] } ``` ### Conventional Commits for Auto-versioning | Prefix | Version Bump | Example | |--------|-------------|---------| | `fix:` | Patch (0.0.x) | `fix: resolve null pointer in auth` | | `feat:` | Minor (0.x.0) | `feat: add user search endpoint` | | `feat!:` / `BREAKING CHANGE:` | Major (x.0.0) | `feat!: change API response format` | ## Common Rationalizations | Excuse | Why It's Wrong | |--------|----------------| | "CI is green, ship it" | CI tests the happy path — verify edge cases, security, and performance separately | | "Manual deploys give us more control" | Manual deploys give you more human error — automate the repeatable parts | | "We'll set up CI when the project is bigger" | Small projects grow fast — CI debt compounds and retrofitting is painful | | "Caching isn't worth the complexity" | Uncached builds waste developer time daily — caching pays for itself in a week | | "Feature flags are over-engineering" | Feature flags decouple deploy from release — they're the cheapest safety net | ## Anti-Patterns - Secrets in pipeline logs or environment dumps - No caching (slow builds) - Running tests only on main (should run on PRs) - Manual deployments to production - No rollback strategy - Skipping linting/type-checking in CI ## Rules - **MUST** include lint, test, and build stages in every pipeline — deploy-only pipelines defer failure to production - **MUST** cache dependencies by lockfile hash, not by branch name — branch keys grow unbounded and leak cache across unrelated work - **NEVER** commit secrets to the pipeline config — use the platform's secret store and reference by name - **NEVER** echo secrets to job logs; GitHub Actions masks known secrets only if they came from `secrets.*`, not from arbitrary env vars - **CRITICAL**: every deployment path has a defined rollback — "we'll figure it out" is not a plan and will cost hours during an incident - **MANDATORY**: PR builds run the same checks as `main` builds; drift between the two hides failures until merge ## Gotchas - GitHub Actions masks secret values sourced from `${{ secrets.X }}` only. Secrets routed through `env:` and then transformed (base64, JSON) lose the mask and appear in logs verbatim. - Workflows triggered by `pull_request` from forks run **without** repository secrets by default (security). Jobs that need secrets either gate on `github.event.pull_request.head.repo.full_name == github.repository` or use `pull_request_target` with explicit code-review — the latter is easy to get wrong and allow token theft. - `actions/cache` restore is best-effort — a cache miss is silent. Jobs that rely on the cache (e.g., skipping tests when nothing changed) must verify cache hits explicitly via the `cache-hit` output. - GitLab CI's `rules:` and `only:/except:` are mutually exclusive at the job level. Mixing parses only at pipeline run, not at `git push`. - Semantic-release assumes a linear history. Merge commits on `main` confuse the commit parser and produce no release — stick to squash merges if you rely on it. ## When NOT to Load - For **generating** a pipeline file for the current project — use `/ci` (this skill is knowledge, not code) - For one-off deployment commands — use `/deploy` - For language-specific build toolchain nuances — pair with `/typescript-patterns`, `/python` rules, etc. - For observability or alerting around deploys — use `/observability-patterns` - For security scanning (SAST, SCA) steps in pipelines — use `/security-patterns` and `/cve-scan`