ci-cd-ops

SKILL.md

CI/CD Operations

Comprehensive patterns for continuous integration, delivery, and deployment using GitHub Actions, release automation tools, and testing pipelines.

GitHub Actions Quick Reference

Workflow File Anatomy

name: CI                          # Display name in Actions tab
on:                               # Trigger events
  push:
    branches: [main]
  pull_request:
    branches: [main]

permissions:                      # GITHUB_TOKEN scope (least privilege)
  contents: read
  pull-requests: write

concurrency:                      # Prevent duplicate runs
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

env:                              # Workflow-level environment variables
  NODE_VERSION: "20"

jobs:
  test:
    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 test

Core Syntax Elements

Element Purpose Example
on Event triggers push, pull_request, schedule
jobs.<id>.runs-on Runner selection ubuntu-latest, self-hosted
jobs.<id>.needs Job dependencies needs: [build, lint]
jobs.<id>.if Conditional execution if: github.event_name == 'push'
jobs.<id>.strategy.matrix Parallel variants node-version: [18, 20, 22]
jobs.<id>.environment Deployment target environment: production
jobs.<id>.permissions Token scope contents: write
steps[*].uses Use an action uses: actions/checkout@v4
steps[*].run Run a command run: npm test
steps[*].env Step environment env: { CI: true }

Trigger Decision Tree

Scenario Trigger Config
Run tests on every PR pull_request branches: [main]
Deploy on merge to main push branches: [main]
Release on version tag push tags: ['v*']
Nightly builds schedule cron: '0 2 * * *'
Manual deployment workflow_dispatch inputs: { environment: ... }
Called by another workflow workflow_call inputs:, secrets:
On PR label change pull_request types: [labeled]
On issue comment issue_comment types: [created]
On release published release types: [published]
On package push registry_package types: [published]

Trigger Filter Patterns

on:
  push:
    branches: [main, 'release/**']      # Branch patterns
    paths: ['src/**', '!src/**/*.test.*'] # Path filters (ignore tests)
    tags: ['v*']                          # Tag patterns
  pull_request:
    types: [opened, synchronize, reopened] # Default types
    paths-ignore: ['docs/**', '*.md']     # Ignore docs-only changes

Caching Strategies

Ecosystem Action / Key Path Restore Key
Node (npm) actions/setup-node with cache: npm Auto Auto
Node (pnpm) actions/setup-node with cache: pnpm Auto Auto
Go modules actions/setup-go with cache: true Auto Auto
Cargo actions/cache@v4 ~/.cargo/registry, target cargo-${{ runner.os }}-${{ hashFiles('Cargo.lock') }}
pip / uv actions/setup-python with cache: pip Auto Auto
Docker layers docker/build-push-action Uses buildx cache type=gha or type=registry
Gradle actions/setup-java with cache: gradle Auto Auto
Composer actions/cache@v4 vendor composer-${{ hashFiles('composer.lock') }}

Manual Cache Example

- uses: actions/cache@v4
  with:
    path: |
      ~/.cargo/bin
      ~/.cargo/registry
      ~/.cargo/git
      target
    key: cargo-${{ runner.os }}-${{ hashFiles('**/Cargo.lock') }}
    restore-keys: |
      cargo-${{ runner.os }}-

Matrix Strategy

strategy:
  fail-fast: false                    # Don't cancel siblings on failure
  max-parallel: 4                     # Limit concurrent jobs
  matrix:
    os: [ubuntu-latest, windows-latest, macos-latest]
    node-version: [18, 20, 22]
    include:                          # Add specific combos
      - os: ubuntu-latest
        node-version: 22
        coverage: true
    exclude:                          # Remove specific combos
      - os: windows-latest
        node-version: 18

Dynamic Matrix

prepare:
  runs-on: ubuntu-latest
  outputs:
    matrix: ${{ steps.set.outputs.matrix }}
  steps:
    - id: set
      run: echo "matrix=$(jq -c . matrix.json)" >> "$GITHUB_OUTPUT"

test:
  needs: prepare
  strategy:
    matrix: ${{ fromJson(needs.prepare.outputs.matrix) }}

Secrets Management

Scope Access Use Case
Repository secrets All workflows in repo API keys, tokens
Environment secrets Jobs targeting that environment Production credentials
Organization secrets Selected repos in org Shared service accounts
OIDC tokens Federated identity Cloud deployment (no stored secrets)

Secrets Best Practices

# Reference secrets - NEVER echo or log them
- run: deploy --token ${{ secrets.DEPLOY_TOKEN }}

# Mask custom values
- run: echo "::add-mask::$CUSTOM_SECRET"

# Use environments for deployment secrets
jobs:
  deploy:
    environment: production           # Requires approval + has secrets
    steps:
      - run: deploy --key ${{ secrets.PROD_API_KEY }}

OIDC for Cloud (No Stored Secrets)

permissions:
  id-token: write
  contents: read

steps:
  - uses: aws-actions/configure-aws-credentials@v4
    with:
      role-to-assume: arn:aws:iam::123456789:role/github-actions
      aws-region: us-east-1

Common Workflow Patterns

Test on Pull Request

name: Test
on:
  pull_request:
    branches: [main]
concurrency:
  group: test-${{ github.head_ref }}
  cancel-in-progress: true
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: 20, cache: npm }
      - run: npm ci
      - run: npm run lint
      - run: npm test -- --coverage

Deploy on Merge to Main

name: Deploy
on:
  push:
    branches: [main]
jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: production
    steps:
      - uses: actions/checkout@v4
      - run: npm ci && npm run build
      - run: npx wrangler deploy
        env:
          CLOUDFLARE_API_TOKEN: ${{ secrets.CF_API_TOKEN }}

Release on Tag

name: Release
on:
  push:
    tags: ['v*']
permissions:
  contents: write
jobs:
  release:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with: { fetch-depth: 0 }
      - run: |
          gh release create ${{ github.ref_name }} \
            --generate-notes \
            --title "${{ github.ref_name }}"
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

Gotchas Table

Gotcha Problem Fix
Shallow clone git describe fails, history missing actions/checkout@v4 with fetch-depth: 0
Default permissions GITHUB_TOKEN is read-only by default Set permissions: explicitly
Action pinning @main can break without warning Pin to SHA: @abc123 or @v4
Fork PR secrets Secrets unavailable on fork PRs Use pull_request_target carefully
Concurrent deploys Race condition on production Use concurrency: groups
Stale caches Cache grows unbounded Include lockfile hash in key
Node.js version setup-node defaults vary Always specify node-version
Docker layer cache Rebuilds everything without cache Use cache-from: type=gha
Matrix + environment Each matrix job needs approval Use a single deploy job after matrix
Path filters + required checks Skipped jobs block merge Use paths-filter action or make checks non-required
GITHUB_TOKEN in PRs Cannot trigger other workflows Use a PAT or GitHub App token
Windows line endings Scripts fail with \r\n Use .gitattributes or core.autocrlf

Expression Syntax Quick Reference

Expression Result
${{ github.event_name }} push, pull_request, etc.
${{ github.ref_name }} Branch or tag name
${{ github.sha }} Full commit SHA
${{ github.actor }} User who triggered
${{ runner.os }} Linux, Windows, macOS
${{ contains(github.event.head_commit.message, '[skip ci]') }} Check commit message
${{ needs.build.outputs.version }} Output from prior job
${{ fromJson(steps.meta.outputs.json) }} Parse JSON output
${{ hashFiles('**/package-lock.json') }} Hash for cache keys
${{ format('refs/heads/{0}', matrix.branch) }} String formatting
${{ toJson(matrix) }} Debug: print matrix config

Step Outputs

steps:
  - id: version
    run: echo "value=$(cat VERSION)" >> "$GITHUB_OUTPUT"

  - run: echo "Version is ${{ steps.version.outputs.value }}"

Job Outputs (for Cross-Job Communication)

jobs:
  build:
    runs-on: ubuntu-latest
    outputs:
      artifact-id: ${{ steps.upload.outputs.artifact-id }}
    steps:
      - id: upload
        run: echo "artifact-id=abc123" >> "$GITHUB_OUTPUT"

  deploy:
    needs: build
    runs-on: ubuntu-latest
    steps:
      - run: echo "Deploying ${{ needs.build.outputs.artifact-id }}"

Reference Files

File Contents
references/github-actions.md Complete workflow syntax, reusable workflows, composite actions, OIDC, runners, debugging
references/release-automation.md Semantic versioning, semantic-release, changesets, goreleaser, changelog, publishing
references/testing-pipelines.md Test stages, parallelism, coverage, service containers, e2e in CI, deployment pipelines
Weekly Installs
2
GitHub Stars
9
First Seen
5 days ago
Installed on
amp2
cline2
openclaw2
opencode2
cursor2
kimi-cli2