AgentStack
Browse Sign in
Browse Why AgentStack Sell Docs
Sign in
SKILL verified MIT Self-run

Cicd Pipelines

skill-san-npm-skills-ws-cicd-pipelines · by san-npm

Production GitHub Actions CI/CD: least-privilege workflows, caching, OIDC deploys, Docker multi-stage builds, blue-green/canary, rollback, release automation (semantic-release, Changesets), and the 2026 supply-chain baseline. Use when writing/reviewing .github/workflows, automating releases, or debugging caching/secrets/OIDC.

No reviews yet
0 installs
43 views
0.0% view→install

Install

$ agentstack add skill-san-npm-skills-ws-cicd-pipelines

✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access Used
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets No
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

View the full security report →

Verified badge

Passed review? Show it. Paste this badge into your README, it links to the public security report.

AgentStack Verified badge Links to your public security report.
[![AgentStack Verified](https://agentstack.voostack.com/badges/verified.svg)](https://agentstack.voostack.com/security/report/skill-san-npm-skills-ws-cicd-pipelines)

Reliability & compatibility

Security review passed
0 installs to date
no reviews yet
2mo ago

Declared compatibility

Claude CodeClaude Desktop

Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.

Preview Execution monitoring

We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.

How agent discovery & health will work →
Are you the author of Cicd Pipelines? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

CI/CD Pipelines

Concrete, runnable patterns for production GitHub Actions pipelines. Every snippet below is self-contained — copy it, swap the placeholders, and ship. Action versions are current as of June 2026; pin by SHA in regulated/high-trust repos (see [Supply-Chain Baseline](#supply-chain-baseline-2026)). For sibling depth on container internals see docker-production; for cloud IAM specifics see aws-production-deploy.

Action Version Matrix (June 2026)

Pin to these majors (or the exact SHA for the major). All listed majors run on the Node 24 runtime — GitHub forced the Actions runtime default to Node 24 on 2026-06-02, so any action still on Node 20 will warn/fail; bump them.

| Action | Pin | Notes | |---|---|---| | actions/checkout | @v6 | v6 changed git config injection; bare git commands still work via includeIf. | | actions/setup-node | @v6 | | | actions/cache | @v5 | | | actions/upload-artifact | @v7 | v7 adds non-zip uploads (compression-level/archive). Not symmetric with download. | | actions/download-artifact | @v8 | Pairs with upload v7; major numbers differ — don't assume they match. | | actions/attest-build-provenance | @v4 | Now a thin wrapper over actions/attest@v4. | | docker/build-push-action | @v7 | v7 = Node 24 default; requires runner ≥ v2.327.1 (GitHub-hosted is fine). | | docker/setup-buildx-action | @v4 | | | docker/login-action | @v4 | | | docker/metadata-action | @v6 | | | aws-actions/configure-aws-credentials | @v6 | | | actions/dependency-review-action | @v5 | | | github/codeql-action | @v3 | v4 exists but v3 is the stable default line; check the docs before jumping. | | codecov/codecov-action | @v7 | Requires CODECOV_TOKEN for public repos since v4. | | sigstore/cosign-installer | @v4 | | | anchore/sbom-action | @v0.24 | 0.x — pin the exact minor, no stable major yet. | | aquasecurity/trivy-action | @v0.36 | 0.x — pin the exact minor. | | step-security/harden-runner | @v2 | Egress filtering / runtime monitoring. | | changesets/action | @v1 (v1.9+) | |

> These move fast. The durable source of truth is each action's releases page; verify before pinning a SHA for production.

GitHub Actions — Core CI Workflow

Set least-privilege permissions at the top level (contents: read) so every job defaults to read-only; grant writes only on the specific job that needs them. This is the single highest-leverage hardening step — a compromised dependency in a test job then cannot push code or mint releases.

# .github/workflows/ci.yml
name: CI
on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

# Least-privilege default for ALL jobs. Override per-job when a job needs more.
permissions:
  contents: read

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

jobs:
  test:
    # Pin the runner image. `ubuntu-latest` silently migrates (e.g. 24.04 -> 26.04)
    # and can break builds mid-sprint. Pin the version; bump it deliberately.
    runs-on: ubuntu-24.04
    strategy:
      fail-fast: false
      matrix:
        # Node 20 reached EOL 2026-04-30 — dropped. 22 = maintenance LTS (until 2027-04),
        # 24 = active LTS. Only matrix versions you actually support in production.
        node: [22, 24]
    steps:
      - uses: actions/checkout@v6
      - uses: actions/setup-node@v6
        with:
          node-version: ${{ matrix.node }}
          cache: npm
      - run: npm ci
      - run: npm test -- --coverage
      - uses: actions/upload-artifact@v7
        with:
          name: coverage-${{ matrix.node }}
          path: coverage/
          retention-days: 7

  # Split lint/typecheck into their own job so they run in parallel with tests,
  # not as sequential steps that serialize the critical path.
  lint:
    runs-on: ubuntu-24.04
    steps:
      - uses: actions/checkout@v6
      - uses: actions/setup-node@v6
        with: { node-version: 24, cache: npm }
      - run: npm ci
      - run: npm run lint
      - run: npm run typecheck

Required status checks & branch protection

CI only protects you if merges are blocked on it. Configure once (Settings → Branches → branch protection rule, or via API/Terraform):

  • Require pull request before merging; require ≥1 approval; dismiss stale approvals on new commits.
  • Require status checks to pass: add the exact job names (test (22), test (24), lint). Matrix jobs register as separate checks — list each, or gate them behind one aggregator job.
  • Require branches to be up to date before merging (forces re-run against latest main).
  • Require signed commits and a linear history on release branches if your compliance posture needs it.
# Aggregator pattern: make ONE check required instead of N flaky matrix entries.
ci-passed:
  runs-on: ubuntu-24.04
  needs: [test, lint]
  if: always()
  steps:
    - name: Fail if any dependency failed
      if: contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled')
      run: exit 1

Caching Strategies

# 1) Node modules — let setup-node manage it (keyed on lockfile hash automatically).
- uses: actions/setup-node@v6
  with: { node-version: 24, cache: npm }   # use 'pnpm' or 'yarn' to match your PM

# 2) pnpm (needs the store + action-setup BEFORE setup-node's cache kicks in)
- uses: pnpm/action-setup@v6
  with: { version: 9 }
- uses: actions/setup-node@v6
  with: { node-version: 24, cache: pnpm }

# 3) Docker layer caching via the GitHub Actions cache backend
- uses: docker/build-push-action@v7
  with:
    context: .
    cache-from: type=gha
    cache-to: type=gha,mode=max     # mode=max also caches intermediate layers

# 4) Turborepo local cache (remote cache is better at scale — see monorepo section)
- uses: actions/cache@v5
  with:
    path: .turbo
    # Include the lockfile in the key so a dep change busts the cache.
    key: turbo-${{ hashFiles('**/turbo.json', '**/package-lock.json') }}-${{ github.sha }}
    restore-keys: |
      turbo-${{ hashFiles('**/turbo.json', '**/package-lock.json') }}-
- run: npx turbo build --cache-dir=.turbo

Cache hygiene: key on the lockfile hash (not loose globs), keep restore-keys as a prefix fallback, and never cache anything secret-derived. Untrusted PRs run in a restricted scope and cannot write to caches/branches your default branch created — don't design a workflow that depends on a PR populating a shared cache.

Secrets & OIDC

Prefer OIDC over long-lived static cloud keys: the workflow mints a short-lived token at runtime, so there is no secret to leak or rotate. id-token: write is required for OIDC and must be granted explicitly (it is not in the contents: read default).

# Repository / org secrets (Settings -> Secrets and variables -> Actions)
env:
  DATABASE_URL: ${{ secrets.DATABASE_URL }}

jobs:
  # Environment-scoped secrets + manual gate. Secrets here are isolated from CI jobs.
  deploy:
    environment: production        # add "Required reviewers" + secrets on this environment
    permissions:
      id-token: write              # mint the OIDC token
      contents: read
    steps:
      # OIDC — no stored cloud keys. Configure the trust policy on the cloud side
      # to only accept tokens from THIS repo + ref (and ideally THIS environment).
      - uses: aws-actions/configure-aws-credentials@v6
        with:
          role-to-assume: arn:aws:iam::123456789012:role/deploy
          aws-region: us-east-1
      - run: ./deploy.sh

Trust-policy scoping (do this — a wildcard repo:* subject is a takeover risk):

// AWS IAM trust policy condition — bind to exactly your repo, ref, and environment
"Condition": {
  "StringEquals": {
    "token.actions.githubusercontent.com:aud": "sts.amazonaws.com",
    "token.actions.githubusercontent.com:sub": "repo:your-org/your-repo:environment:production"
  }
}

Secret rules:

  • Never echo/print a secret; GitHub masks known values but interpolation can defeat masking.
  • Never pass secrets into actions triggered by pull_request from forks (use pull_request_target only with extreme care — it runs with write scope against untrusted code).
  • Prefer the auto-provisioned GITHUB_TOKEN (scoped, short-lived) over a PAT. If you need a PAT, use a fine-grained token with minimal repo/permission scope and a short expiry.
  • Rotate any unavoidable static credential on a schedule and alert on use from unexpected IPs (step-security/harden-runner@v2 can enforce egress allowlists).

Docker Multi-Stage Build

The classic footgun: npm ci in the build stage (dev deps included for the build), then copying node_modules straight into the runtime image — shipping dev dependencies, tooling, and a larger attack surface to production. Fix it with a dedicated deps stage that installs production-only, and copy that into runtime.

# syntax=docker/dockerfile:1

# --- deps: production-only dependencies for the runtime image ---
FROM node:24-alpine AS deps
WORKDIR /app
COPY package*.json ./
# BuildKit cache mount keeps the npm cache warm across builds without baking it in.
RUN --mount=type=cache,target=/root/.npm \
    npm ci --omit=dev

# --- build: full deps (incl. dev) just to compile, never shipped ---
FROM node:24-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN --mount=type=cache,target=/root/.npm npm ci
COPY . .
RUN npm run build

# --- runtime: minimal, non-root, prod deps only ---
FROM node:24-alpine AS runtime
WORKDIR /app
ENV NODE_ENV=production
RUN addgroup -g 1001 app && adduser -u 1001 -G app -s /bin/sh -D app
COPY --from=deps  /app/node_modules ./node_modules   # PROD deps only
COPY --from=build /app/dist          ./dist
COPY package.json ./
USER app
EXPOSE 3000
# Container-level liveness; pair with your orchestrator's probes.
HEALTHCHECK --interval=30s --timeout=3s --start-period=20s --retries=3 \
  CMD node -e "fetch('http://127.0.0.1:3000/health').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"
CMD ["node", "dist/index.js"]

Build, scan, sign, and attest in CI

build-image:
  runs-on: ubuntu-24.04
  permissions:
    contents: read
    packages: write        # push to GHCR
    id-token: write        # keyless cosign signing + attestations
    attestations: write    # actions/attest-build-provenance
  steps:
    - uses: actions/checkout@v6
    - uses: docker/setup-buildx-action@v4
    - uses: docker/login-action@v4
      with:
        registry: ghcr.io
        username: ${{ github.actor }}
        password: ${{ secrets.GITHUB_TOKEN }}
    - id: meta
      uses: docker/metadata-action@v6
      with:
        images: ghcr.io/${{ github.repository }}
        tags: |
          type=sha
          type=semver,pattern={{version}}
    - id: build
      uses: docker/build-push-action@v7
      with:
        context: .
        push: true
        tags: ${{ steps.meta.outputs.tags }}
        labels: ${{ steps.meta.outputs.labels }}
        cache-from: type=gha
        cache-to: type=gha,mode=max
    # Vulnerability scan — fail the build on fixable HIGH/CRITICAL CVEs.
    - uses: aquasecurity/trivy-action@v0.36
      with:
        image-ref: ghcr.io/${{ github.repository }}@${{ steps.build.outputs.digest }}
        format: sarif
        output: trivy.sarif
        severity: HIGH,CRITICAL
        ignore-unfixed: true
        exit-code: '1'
    # Keyless image signing (Sigstore/Fulcio — no key to manage).
    - uses: sigstore/cosign-installer@v4
    - run: cosign sign --yes ghcr.io/${{ github.repository }}@${{ steps.build.outputs.digest }}
    # Build provenance attestation (SLSA) bound to the pushed digest.
    - uses: actions/attest-build-provenance@v4
      with:
        subject-name: ghcr.io/${{ github.repository }}
        subject-digest: ${{ steps.build.outputs.digest }}
        push-to-registry: true

Always reference images by digest (@sha256:...), never a mutable tag, downstream — that is what cosign signs and what provenance attests. Verify at deploy time: cosign verify --certificate-identity-regexp '...' --certificate-oidc-issuer https://token.actions.githubusercontent.com IMAGE@DIGEST.

Deployment Strategies

| Strategy | Downtime | Rollback Speed | Risk | Best For | |---|---|---|---|---| | Rolling | Zero | Minutes | Medium | Stateless services | | Blue-Green | Zero | Instant (swap) | Low | Critical services | | Canary | Zero | Fast (shift back) | Lowest | High-traffic APIs | | Recreate | Yes | Slow | High | Dev/staging only |

Blue-Green with GitHub Actions

deploy:
  runs-on: ubuntu-24.04
  environment: production
  permissions: { id-token: write, contents: read }
  steps:
    - uses: actions/checkout@v6
    - name: Deploy to idle (green) slot
      run: ./deploy.sh green
    - name: Health check green before any traffic
      run: |
        for i in $(seq 1 30); do
          curl -fsS https://green.app.example/health && exit 0
          sleep 5
        done
        echo "green never became healthy"; exit 1
    - name: Swap traffic to green
      run: ./swap-traffic.sh green
    - name: Keep blue warm as instant rollback
      run: echo "Rollback = ./swap-traffic.sh blue (previous version still running)"

Canary (progressive traffic shift)

canary:
  runs-on: ubuntu-24.04
  environment: production
  steps:
    - uses: actions/checkout@v6
    - run: ./deploy.sh canary
    - name: Shift 5% → watch SLOs → 25% → 50% → 100%
      run: |
        for pct in 5 25 50 100; do
          ./set-weight.sh canary "$pct"
          sleep 120
          # Bail (and auto-rollback) if error rate / latency SLO breaches.
          ./check-slo.sh canary || { ./set-weight.sh canary 0; exit 1; }
        done

Environment Promotion (dev → staging → prod)

# Trigger chain: push to main → dev → staging (auto) → prod (manual approval)
deploy-dev:
  if: github.ref == 'refs/heads/main'
  environment: dev
  permissions: { id-token: write, contents: read }

deploy-staging:
  needs: deploy-dev
  environment: staging
  permissions: { id-token: write, contents: read }

deploy-prod:
  needs: deploy-staging
  environment: production   # set "Required reviewers" + a wait timer on this environment
  permissions: { id-token: write, contents: read }

Release Automation

Option A — semantic-release (single package, automated versioning from commits)

semantic-release reads Conventional Commits, computes the next version, publishes to npm, creates the GitHub release, and commits the changelog — all in CI on main. The common failure is a release job missing Node setup, a clean install, or the npm auth token, so it either can't run or publishes unauthenticated.

// .releaserc.json
{
  "branches": ["main", { "name": "next", "prerelease": true }],
  "plugins": [
    "@semantic-release/commit-analyzer",
    "@semantic-release/release-notes-generator",
    "@semantic-release/changelog",
    "@semantic-release/npm",
    "@semantic-release/github",
    ["@semantic-release/git", { "assets": ["CHANGELOG.md", "package.json"] }]
  ]
}
# .github/workflows/release.yml
name: Release
on:
  push:
    branches: [main]

permissions:
  contents: read   # least-privilege default; the release job widens it below

jobs:
  release:
    runs-on: ubuntu-24.04
    permissions:
      contents: write       # push the changelog/version commit + create the GitHub release
      issues: write         # comment on released issues
      pull-requests: write  # comment on released PRs
      id-token: write       # npm provenance (publish with verifiable origin)
    steps:
      - uses: actions/checkout@v6
        with:
          fetch-depth: 0     # full history — semantic-release diffs all tags
          persist-credentials: false
      - uses: actions/setup-node@v6
        with:
          node-version: 24
          cache: npm

…

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [san-npm](https://github.com/san-npm)
- **Source:** [san-npm/skills-ws](https://github.com/san-npm/skills-ws)
- **License:** MIT
- **Homepage:** https://skills-ws.vercel.app

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet, be the first.

Versions

  • v0.1.0 Imported from the upstream source.