CI/CD Pipelines: Multi-Platform Production Infrastructure
Write, review, and architect CI/CD pipelines across GitHub Actions, GitLab CI/CD, Forgejo
Actions, Gitea Actions, and Woodpecker. The goal is secure, fast, auditable pipelines that
satisfy both engineering needs and compliance requirements (PCI-DSS 4.0).
Target versions: May 2026 snapshot. Read references/target-versions.md before
pinning forge, runner, CI, or supply-chain tool versions.
This skill covers workflow design, security, compliance, cross-platform migration,
runners, dependency updates, scanning, review gates, and rollout order.
When to use
Writing or reviewing CI/CD pipeline configs (GitHub/Forgejo/Gitea Actions, .gitlab-ci.yml, .woodpecker/*.yaml)
Migrating pipelines between platforms (GitLab -> GitHub, GitHub -> Forgejo)
Troubleshooting failed pipelines, flaky jobs, or runner issues
When NOT to use
Kubernetes manifests, Helm charts, cluster architecture - use kubernetes
Dockerfiles, Compose stacks, container image optimization - use docker
Terraform/OpenTofu infrastructure-as-code - use terraform
Ansible playbooks, configuration management - use ansible
Security audits of application code (SAST findings, auth bugs) - use security-audit
Code review of pipeline-adjacent code (the app itself) - use code-review
The code-review skill has a cicd-pipelines.md reference for bug patterns in existing
pipelines. This skill is for writing and architecting pipelines.
AI Self-Check
AI tools consistently produce the same CI/CD mistakes. Before returning any generated
pipeline config, verify against this list.
Review mode: if auditing an existing pipeline rather than generating one, invert this
checklist - each item that fails is a finding. Work through the list top-to-bottom and report
every failure with file and line reference.
SHA pinning: all third-party actions/images pinned to full commit SHA or digest, not mutable tags. Add # vX.Y.Z comment for readability.
Permissions: explicit permissions: block on every GitHub Actions workflow (read-only default). GitLab: protected variables scoped correctly.
No secrets in config: no hardcoded tokens, passwords, or API keys. Use CI/CD secret variables or vault integration.
No latest tags: runner images, tool images, and base images pinned to specific versions or SHA256 digests.
Fail-fast security: SAST, dependency scanning, and secret detection run early (not after deployment).
Manual gates for production: production deployments require explicit approval (not auto-deploy on merge).
SBOM generation: release pipelines generate and attach SBOMs (SPDX or CycloneDX). Required for PCI-DSS 4.0.
Minimal scope: jobs have minimum required permissions, access only needed secrets, and run only needed steps.
No allow_failure without justification: if a job can fail, explain why in a comment.
Version pinning on tools: node:22, not node:lts. python:3.13, not python:3. Specific versions prevent silent breakage.
Trigger scoping: on: push without branch/path filters runs on every push to every branch - scope to branches: [main] and/or paths: filters. Same for GitLab: rules: with if conditions, not bare only: [pushes].
No expression injection (GitHub Actions): ${{ }} expressions never used directly in run: blocks. Assign to env: first. github.event.* is attacker-controlled. Avoid github.ref_name in security-sensitive contexts (injectable via crafted tag/branch names).
Self-hosted runners ephemeral on public/untrusted repos: non-ephemeral shell runners on repos that accept outside PRs is the top self-hosted-runner compromise vector. Verify --ephemeral (GitHub, Gitea) or capacity-based single-job runners (Forgejo) + approval gates for outside contributors. See references/runners.md.
Docker socket mount scope: /var/run/docker.sock mounted into a job gives it root on the host. Only acceptable for trusted internal pipelines. Public/shared runners need DinD sidecar or rootless buildkit instead.
Scan gate has a baseline, not a blanket block: container/IaC/SAST scanners introduced with exit-code 1 and zero suppression always get disabled. Use the ratchet pattern (non-blocking -> baseline -> block new only) from references/best-practices.md.
Ignore-list entries have expiry dates: every .trivyignore, .grype.yaml, Dependabot ignore, or Renovate ignoreDeps entry includes a comment with revisit date + owner. No dates = zombie tech debt.
Lockfiles committed: package-lock.json, bun.lock, Cargo.lock, go.sum, uv.lock belong in version control for applications. Manifest-only commits break reproducibility.
Auto-merge gated on tests, not just lint: Dependabot/Renovate auto-merge without test coverage of the changed area is a supply-chain shortcut.
Current source checked: dated versions, CLI flags, API names, and support windows are verified against primary docs before repeating them
Hidden state identified: local config, credentials, caches, contexts, branches, cluster targets, or previous runs are made explicit before acting
Verification is real: final checks exercise the actual runtime, parser, service, or integration point instead of only linting prose or happy paths
Routing overlap checked: overlapping skills, trigger terms, and "When NOT to use" boundaries are checked before returning guidance
Spec claims verified: claims about tool behavior, output contracts, or repo conventions are checked against current docs, scripts, or skill files
Rule: cache is a speed optimization, not a correctness mechanism. Artifacts are for
inter-job data. Cache may evict at any time - pipelines must work without it.
Protected branches/tags, environments, masked in logs.
Forgejo
Repository/org secrets
Per-repo, per-org. No environment scoping yet.
All platforms: never echo secrets, never pass as CLI args (visible in ps), never write
to artifacts. Use environment variables or file-based injection.
Per-service workflows with paths: filters (simplest, recommended)
Single workflow with matrix + change detection job that outputs which services need building
Rule: always rebuild when the shared lib changes. A "nothing changed" optimization that misses a shared dependency is worse than rebuilding everything.
For Python monorepos with multiple services sharing a common library (libs/common/):
Cache the resolver output, not the install step. Key on hashFiles('**/requirements*.txt') or **/poetry.lock/**/uv.lock. With uv or pip, cache ~/.cache/uv or ~/.cache/pip plus each service's .venv/ keyed on the service path + lockfile hash.
Install the shared lib editable (pip install -e libs/common) so services import the in-repo version, not a stale wheel.
Scope jobs per service with path filters. GitLab: rules: - changes: paths: ['services/api/**', 'libs/common/**'] compare_to: refs/heads/main. GitHub/Forgejo: on.push.paths / on.pull_request.paths. Always include libs/common/** in every service's filter so a shared-lib change triggers all services.
YAML anchors (GitLab) / reusable workflows (GitHub) for the per-service job template. Three near-identical blocks for api, worker, scheduler is a maintenance trap.
See references/gitlab-ci.md for a full monorepo .gitlab-ci.yml (YAML anchors, compare_to, per-service change rules, shared-lib detection).
Forgejo CI/CD
Forgejo Actions is "designed to be familiar, not designed to be compatible" with GitHub Actions.
It reuses the workflow syntax but makes no compatibility guarantees.
Key differences from GitHub Actions
Feature
GitHub Actions
Forgejo Actions
permissions:
Controls GITHUB_TOKEN scope
Not enforced - token always has full rw (read-only for fork PRs)
continue-on-error: (job level)
Allows job failure without failing workflow
Not supported - step-level only
Runner images
Managed ubuntu-24.04 with 200+ tools
Self-hosted, typically lean Debian/Alpine
Action resolution
actions/checkout@v4 -> github.com
Resolves from Forgejo mirror (configurable)
OIDC
permissions: id-token: write
enable-openid-connect key
Workflow call defaults
inputs.<id>.default populated
Always empty
Matrix + dynamic runs-on
Supported
Supported since v14.0
LXC execution
Not supported
Supported (Forgejo-specific)
Forgejo workflow template
name: CI
on:
push:
branches: [main]
pull_request:
jobs:
ci:
runs-on: docker # self-hosted runner label
container:
image: oven/bun:1.2 # pin to minor version minimum
steps:
- uses: actions/checkout@<sha> # pin to SHA; resolves from Forgejo mirror
- run: bun install --frozen-lockfile
- run: bun run lint
- run: bun run typecheck
- run: bun run test
Forgejo action SHA discovery
Forgejo resolves actions from its own mirror or a configured upstream, not from github.com.
Finding the correct SHA for a self-hosted mirror requires different steps than GitHub.
Find the SHA on your Forgejo instance:
# List tags and their SHAs from the Forgejo mirror
git ls-remote https://forgejo.example.com/actions/checkout.git 'refs/tags/v4*'
# Or use the Forgejo API to get a tag's commit SHA
curl -s https://forgejo.example.com/api/v1/repos/actions/checkout/git/refs/tags/v4.2.2 \
| jq -r '.object.sha'
If your instance mirrors from code.forgejo.org (the default upstream):
# Clone at the specific SHA and inspect
git clone --depth 1 https://forgejo.example.com/actions/checkout.git /tmp/checkout-verify
cd /tmp/checkout-verify
git checkout <sha>
# Review action.yml and dist/ - compare against the known-good upstream release
Key differences from GitHub SHA discovery:
The same action (e.g., actions/checkout) may have different SHAs on Forgejo mirrors vs GitHub
because Forgejo forks maintain their own commits
code.forgejo.org/actions/* repos are Forgejo-maintained forks, not exact copies of GitHub repos
Always verify SHAs against your own instance, not against github.com
If the action repo is not mirrored yet, an admin must add it to the Forgejo mirror list
Forgejo-specific gotchas
No ubuntu-latest - runs-on maps to your registered runner labels (e.g., docker)
Missing tools - Forgejo runner containers are lean. Add apt-get install for git, curl, etc.
TLS certs - if Forgejo uses self-signed or internal CA certs, configure the runner's trust
store (GIT_SSL_CAINFO=/path/to/ca-bundle.crt) or install the CA into the container image.
GIT_SSL_NO_VERIFY=true is a last resort for dev/test only - never normalize TLS bypass in production
Third-party actions - many GitHub Marketplace actions use GitHub-specific API calls and will silently fail
Secrets in Forgejo - ${{ secrets.* }} works, but no environment-level scoping
permissions: not enforced - Forgejo parses the field but does not restrict the workflow token.
The token always has full read-write access (read-only for fork PRs only). Don't assume
least-privilege from permissions: alone - it has no effect on Forgejo.
Managing Forgejo Actions with fj
The community Forgejo CLI (fj, v0.4.1+) covers the day-to-day Actions surface: listing
runs, dispatching workflows, and managing variables/secrets. It is much faster than the web
UI for bulk secret updates and scriptable for one-shot runs. Install and auth details live
in the git skill's forge-workflows.md reference.
# List recent runs (for a quick "is CI green on main?" check)
fj actions tasks
# Trigger a workflow_dispatch run without opening the browser
fj actions dispatch publish.yaml main --inputs version=1.2.3
# Bulk variable/secret management (writes to the repo scope)
fj actions variables create CACHE_BUCKET gs://my-bucket
fj actions secrets create REGISTRY_TOKEN "$REGISTRY_TOKEN"
What fj does not do yet (as of 0.4.1): stream runner logs, re-run failed jobs, cancel
running tasks. For those, use the web UI or hit /api/v1/repos/{owner}/{repo}/actions/tasks/{id}
directly. Log streaming across the fleet still belongs in your observability stack, not fj.
On Gitea instead of Forgejo? Use tea (gitea.com/gitea/tea) - the Gitea CLI covers
a similar surface (issues, PRs, releases) against any Gitea 1.20+ instance. Gitea Actions
lacks fj-equivalent CLI tooling; use the web UI or API. If you're running Forgejo,
prefer fj - it tracks Forgejo-specific behavior (AGit, Forgejo Actions quirks) that
tea does not.
Gitea CI/CD
Gitea ships two viable CI paths: Gitea Actions (same act-based engine as Forgejo
Actions, since Gitea 1.21) and Woodpecker CI (separate service, container-native,
webhook-driven). Drone is legacy - do not start new installs.
Quick rule of thumb: if you are migrating from GitHub or want one service to operate,
use Gitea Actions. If you need proper matrix builds, caching primitives, or lighter
resource usage, use Woodpecker. Do not run both against the same repo.
See references/forgejo-gitea-actions.md for: action SHA discovery, Gitea-vs-Forgejo Actions
differences, Woodpecker YAML examples, plugin vs command steps, OAuth setup, matrix
patterns, and Drone migration guidance.
Forgejo release workflow pattern
name: Release
on:
push:
tags: ['v*']
jobs:
build-and-push:
runs-on: docker
container:
image: catthehacker/ubuntu:act-24.04 # heavier image for multi-tool needs
# Private-forge TLS: mount your CA and set GIT_SSL_CAINFO=/path/to/ca.crt.
# GIT_SSL_NO_VERIFY is a dev/test-only last resort - never commit it to a release pipeline.
steps:
- uses: actions/checkout@<sha> # pin to SHA; resolves from Forgejo mirror
- name: Login to registry
env:
TOKEN: ${{ secrets.REGISTRY_TOKEN }}
HOST: ${{ secrets.REGISTRY_HOST }}
USER: ${{ secrets.REGISTRY_USER }}
run: echo "$TOKEN" | docker login "$HOST" -u "$USER" --password-stdin
- name: Build and push
env:
REGISTRY: ${{ secrets.REGISTRY_HOST }}/${{ secrets.REGISTRY_IMAGE }}
TAG: ${{ github.ref_name }}
run: |
docker build -t "$REGISTRY:$TAG" .
docker push "$REGISTRY:$TAG"
Note: use secrets for registry host/image to avoid hardcoding private domains in git history.
PCI-DSS 4.0: CI/CD Compliance Mapping
All future-dated requirements became mandatory March 31, 2025.
PCI-DSS Req
What it means for CI/CD
Implementation
6.2.1
Secure development training + OWASP-aware processes
SAST on every PR/MR, dependency scanning, secret detection
6.2.4
Access control + change tracking
Branch protection, required reviewers, signed commits, audit logs
6.3.2
Software inventory (SBOM)
Generate SPDX/CycloneDX SBOM per release, attach to artifacts
6.4.2
Changes approved, documented, tested
Gated deployments, required approvals for prod, IaC audit trails
6.5.3
Consistent security controls across environments
Same scanning in dev/staging/prod, not just prod
Customized Approach (v4.0.1): automated CI/CD controls can satisfy manual review requirements
if properly documented. An automated SAST/DAST/SCA gate with evidence = equivalent to manual
code review for QSA assessment.
Read references/supply-chain.md for detailed PCI-DSS compliance patterns.
AI-Age Considerations
AI tools consistently generate insecure CI/CD configs: unpinned actions, missing permissions:
blocks, allow_failure: true without justification, :latest tags, secrets in run: blocks.
Always run the AI Self-Check against AI-generated pipeline code.
For detailed coverage of slopsquatting, AI agents in CI/CD, prompt injection in pipelines, and
the OWASP Top 10 for Agentic Applications, read references/supply-chain.md
(AI-Age Supply Chain Risks section).
Template Conventions
@<sha> in GitHub Actions templates is a placeholder. Replace with the real 40-character
commit SHA for the indicated version. Look up SHAs on the action's releases page or use
Dependabot to manage them automatically.
Image tags in templates use floating minor versions (e.g., oven/bun:1.2, docker:27.5)
for readability. For production, pin to a specific patch version or SHA256 digest. The templates
show the minimum acceptable granularity, not the ideal.
Reference Files
references/github-actions.md - GitHub Actions patterns, templates, and security hardening
references/target-versions.md - May 2026 version snapshot for forges, runners, CI systems, and supply-chain tools
Output Contract
See skills/_shared/output-contract.md for the full contract.
Skill name: CI-CD
Deliverable bucket:audits
Mode: conditional. When invoked to analyze, review, audit, or improve existing repo content, emit the full contract - boxed inline header, body summary inline plus per-finding detail in the deliverable file, boxed conclusion, conclusion table - and write the deliverable to docs/local/audits/ci-cd/<YYYY-MM-DD>-<slug>.md. When invoked to answer a question, teach a concept, build a new artifact, or generate content, respond freely without the contract.
Severity scale:P0 | P1 | P2 | P3 | info (see shared contract; only used in audit/review mode).
Related Skills
code-review - has a cicd-pipelines.md reference for CI/CD bug patterns (expression
injection, variable scoping, cache gotchas, ArgoCD sync issues)
security-audit - for auditing application code, not pipeline code
docker - for Dockerfile and container image optimization
kubernetes - for K8s manifests and Helm charts that pipelines deploy to
git - for git operations (commits, PRs/MRs, tags, releases) that trigger pipelines.
CI/CD reacts to git events; git handles the operations that produce them.
Rules
Platform-first. Always confirm which CI/CD platform before writing config. GitHub Actions
syntax that "mostly works" in Forgejo will silently break on edge cases.
SHA-pin everything. All third-party actions, all CI tool images. Tags are mutable. SHAs are not.
The tj-actions, reviewdog, and Trivy compromises proved this is non-negotiable.
Secrets are sacred. Never log, echo, artifact, or pass as CLI arguments. Never use
protected variables on unprotected branches.
Test the pipeline itself.act (GitHub Actions local runner), gitlab-ci-local, or dry-run
modes. Don't discover pipeline bugs in production.
Cache != artifact. Cache is ephemeral speed optimization. Artifacts are guaranteed inter-job
data. Confusing them causes intermittent failures.
Manual gates for prod. No exceptions. Auto-deploy to staging is fine. Auto-deploy to
production is how incidents happen.
Scan early, deploy late. Security scanning in the first stages, deployment in the last.
Finding a CVE after deployment is expensive.
PCI-DSS 4.0 is mandatory. If the pipeline touches CDE (cardholder data environment),
SBOM generation, signed artifacts, and gated deployments are not optional.
1---2name: ci-cd-83description: · Write/review CI/CD for GitHub Actions, GitLab, Forgejo/Gitea, Woodpecker. Triggers: 'ci/cd', 'pipeline', 'github actions', 'gitlab ci', 'runner', 'renovate', 'trivy'. Not for git workflows (use git).4license: MIT5---67# CI/CD Pipelines: Multi-Platform Production Infrastructure
89Write, review, and architect CI/CD pipelines across GitHub Actions, GitLab CI/CD, Forgejo
10Actions, Gitea Actions, and Woodpecker. The goal is secure, fast, auditable pipelines that
11satisfy both engineering needs and compliance requirements (PCI-DSS 4.0).
1213**Target versions**: May 2026 snapshot. Read `references/target-versions.md` before
14pinning forge, runner, CI, or supply-chain tool versions.
1516This skill covers workflow design, security, compliance, cross-platform migration,
17runners, dependency updates, scanning, review gates, and rollout order.
1819## When to use
2021- Writing or reviewing CI/CD pipeline configs (GitHub/Forgejo/Gitea Actions, `.gitlab-ci.yml`, `.woodpecker/*.yaml`)
22- Designing pipeline architecture (stages, parallelism, caching, deployment strategies)
23- Hardening pipelines against supply chain attacks (SHA pinning, image signing, provenance)
24- Setting up security scanning in CI (SAST, SCA, container scanning, secret detection)
25- Configuring runners (install, register, executor choice, hardening) - see `references/runners.md`
26- Setting up caching strategies or artifact management
27- PCI-DSS 4.0 compliance for CI/CD (Req 6.2.1, 6.2.4, 6.3.2, 6.4.2, 6.5.3)
28- Migrating pipelines between platforms (GitLab -> GitHub, GitHub -> Forgejo)
29- Troubleshooting failed pipelines, flaky jobs, or runner issues
3031## When NOT to use
3233- Kubernetes manifests, Helm charts, cluster architecture - use **kubernetes**
34- Dockerfiles, Compose stacks, container image optimization - use **docker**
35- Terraform/OpenTofu infrastructure-as-code - use **terraform**
36- Ansible playbooks, configuration management - use **ansible**
37- Security audits of application code (SAST findings, auth bugs) - use **security-audit**
38- Code review of pipeline-adjacent code (the app itself) - use **code-review**
39- The code-review skill has a `cicd-pipelines.md` reference for **bug patterns** in existing
40 pipelines. This skill is for **writing and architecting** pipelines.
4142## AI Self-Check
4344AI tools consistently produce the same CI/CD mistakes. **Before returning any generated
45pipeline config, verify against this list.**
4647**Review mode:** if auditing an existing pipeline rather than generating one, invert this
48checklist - each item that fails is a finding. Work through the list top-to-bottom and report
49every failure with file and line reference.
5051- [ ] **SHA pinning**: all third-party actions/images pinned to full commit SHA or digest, not mutable tags. Add `# vX.Y.Z` comment for readability.
52- [ ] **Permissions**: explicit `permissions:` block on every GitHub Actions workflow (read-only default). GitLab: protected variables scoped correctly.
53- [ ] **No secrets in config**: no hardcoded tokens, passwords, or API keys. Use CI/CD secret variables or vault integration.
54- [ ] **No `latest` tags**: runner images, tool images, and base images pinned to specific versions or SHA256 digests.
55- [ ] **Caching strategy**: dependencies cached correctly (lockfile-based keys), build outputs use artifacts (not cache).
56- [ ] **Fail-fast security**: SAST, dependency scanning, and secret detection run early (not after deployment).
57- [ ] **Manual gates for production**: production deployments require explicit approval (not auto-deploy on merge).
58- [ ] **SBOM generation**: release pipelines generate and attach SBOMs (SPDX or CycloneDX). Required for PCI-DSS 4.0.
59- [ ] **Minimal scope**: jobs have minimum required permissions, access only needed secrets, and run only needed steps.
60- [ ] **No `allow_failure` without justification**: if a job can fail, explain why in a comment.
61- [ ] **Version pinning on tools**: `node:22`, not `node:lts`. `python:3.13`, not `python:3`. Specific versions prevent silent breakage.
62- [ ] **Trigger scoping**: `on: push` without branch/path filters runs on every push to every branch - scope to `branches: [main]` and/or `paths:` filters. Same for GitLab: `rules:` with `if` conditions, not bare `only: [pushes]`.
63- [ ] **No expression injection** (GitHub Actions): `${{ }}` expressions never used directly in `run:` blocks. Assign to `env:` first. `github.event.*` is attacker-controlled. Avoid `github.ref_name` in security-sensitive contexts (injectable via crafted tag/branch names).
64- [ ] **Self-hosted runners ephemeral on public/untrusted repos**: non-ephemeral shell runners on repos that accept outside PRs is the top self-hosted-runner compromise vector. Verify `--ephemeral` (GitHub, Gitea) or capacity-based single-job runners (Forgejo) + approval gates for outside contributors. See `references/runners.md`.
65- [ ] **Docker socket mount scope**: `/var/run/docker.sock` mounted into a job gives it root on the host. Only acceptable for trusted internal pipelines. Public/shared runners need DinD sidecar or rootless buildkit instead.
66- [ ] **Scan gate has a baseline, not a blanket block**: container/IaC/SAST scanners introduced with `exit-code 1` and zero suppression always get disabled. Use the ratchet pattern (non-blocking -> baseline -> block new only) from `references/best-practices.md`.
67- [ ] **Ignore-list entries have expiry dates**: every `.trivyignore`, `.grype.yaml`, Dependabot `ignore`, or Renovate `ignoreDeps` entry includes a comment with revisit date + owner. No dates = zombie tech debt.
68- [ ] **Lockfiles committed**: `package-lock.json`, `bun.lock`, `Cargo.lock`, `go.sum`, `uv.lock` belong in version control for applications. Manifest-only commits break reproducibility.
69- [ ] **Auto-merge gated on tests, not just lint**: Dependabot/Renovate auto-merge without test coverage of the changed area is a supply-chain shortcut.
70- [ ] **Current source checked**: dated versions, CLI flags, API names, and support windows are verified against primary docs before repeating them
71- [ ] **Hidden state identified**: local config, credentials, caches, contexts, branches, cluster targets, or previous runs are made explicit before acting
72- [ ] **Verification is real**: final checks exercise the actual runtime, parser, service, or integration point instead of only linting prose or happy paths
73- [ ] **Routing overlap checked**: overlapping skills, trigger terms, and "When NOT to use" boundaries are checked before returning guidance
74- [ ] **Spec claims verified**: claims about tool behavior, output contracts, or repo conventions are checked against current docs, scripts, or skill files
75- [ ] **Runner trust checked**: workflow advice distinguishes hosted, self-hosted, fork, and protected-branch execution
76- [ ] **Mutable references controlled**: actions, images, includes, and templates are pinned where supply-chain risk matters
7778## Performance
7980- Key caches by lockfiles and toolchain versions; avoid broad caches that restore stale dependencies.
81- Split quick lint/unit gates from slow integration, image, and deployment jobs.
82- Use path filters and matrix limits to keep monorepo pipelines proportional to the change.
8384## Best Practices
8586- Use OIDC or short-lived federation for cloud deploys instead of long-lived static secrets.
87- Keep pull-request workflows from forks read-only unless explicitly isolated.
88- Generate provenance or attestations for release artifacts where the forge supports it.
8990## Workflow
9192### Step 1: Identify the platform
9394| Signal | Platform |
95|--------|----------|
96| `.github/workflows/*.yml` | GitHub Actions |
97| `.gitlab-ci.yml` | GitLab CI/CD |
98| `.forgejo/workflows/*.yml` | Forgejo Actions |
99| `.gitea/workflows/*.yml` | Gitea Actions |
100| `.woodpecker/*.yaml` or `.woodpecker.yaml` | Woodpecker (Gitea/Forgejo) |
101| User says "work" / "gitlab" / `glab` | GitLab CI/CD |
102| User says "home" / "forgejo" / `fj` | Forgejo Actions |
103| User says "gitea" | Gitea Actions (or Woodpecker if 1.20 or older) |
104| User says "github" / "ghcr" / `gh` | GitHub Actions |
105106If unclear, ask. The platforms have significant differences despite surface similarity.
107108### Step 2: Determine the domain
109110- **"Create a CI pipeline for my project"** -> Workflow design
111- **"Harden my pipeline" / "pin actions"** -> Security
112- **"Make this PCI compliant" / "SBOM"** -> Compliance
113- **"Port this from GitLab to GitHub"** -> Cross-platform
114115### Step 3: Gather requirements
116117Before writing pipeline config:
118- **What triggers the pipeline?** Push, PR/MR, tag, schedule, manual
119- **What does it build?** Language, runtime, package manager, build tool
120- **What does it test?** Unit, integration, e2e, linting, typechecking
121- **Where does it deploy?** K8s, Docker registry, cloud, bare metal
122- **What compliance requirements?** PCI-DSS, SOC 2, internal policies
123- **Self-hosted or managed runners?** Affects available tools and caching
124125### Step 4: Apply platform-specific patterns
126127Read the appropriate reference file:
128- **GitHub Actions**: `references/github-actions.md`
129- **GitLab CI/CD**: `references/gitlab-ci.md`
130- **Forgejo/Gitea Actions and Woodpecker**: `references/forgejo-gitea-actions.md`
131- **Self-hosted runners** (all 5 implementations): `references/runners.md`
132- **Best practices** (deps, linting, scanning, review gates, rollout): `references/best-practices.md`
133- **Supply chain / compliance**: `references/supply-chain.md`
134135For **Forgejo CI/CD**, see the Forgejo section below (smaller scope, inline).
136137### Step 5: Verify against AI Self-Check
138139Run through the checklist above before returning any generated config.
140141## Cross-Platform Patterns
142143### Stage ordering (all platforms)
144145```
146lint -> test -> build -> scan -> deploy
147```
1481491. **Lint** first - fastest feedback, catches formatting/syntax early
1502. **Test** - unit tests, typechecking
1513. **Build** - compile, bundle, create artifacts
1524. **Scan** - SAST, dependency audit, container scan (on build output)
1535. **Deploy** - staging auto, production manual
154155### Caching strategy
156157| What | Cache key | Platform notes |
158|------|-----------|----------------|
159| **npm/bun** | `${{ hashFiles('**/package-lock.json') }}` or lockb | GH: `actions/cache`. GL: `cache:key:files`. Forgejo: same as GH. |
160| **pip** | `${{ hashFiles('**/requirements*.txt') }}` | GH: `setup-python` with `cache: pip`. GL: cache `~/.cache/pip`. |
161| **poetry** | `${{ hashFiles('**/poetry.lock') }}` | GH: `setup-python` with `cache: poetry`. GL: cache `~/.cache/pypoetry`. |
162| **uv** | `${{ hashFiles('**/uv.lock') }}` | GH: `astral-sh/setup-uv` has built-in cache. GL: cache `~/.cache/uv`. |
163| **Go** | `${{ hashFiles('**/go.sum') }}` | GH: `actions/setup-go` has built-in cache. |
164| **Docker layers** | BuildKit cache mount or registry cache | GH: `--cache-from type=gha`. GL: `--cache-from $CI_REGISTRY_IMAGE:cache`. |
165166**Rule**: cache is a speed optimization, not a correctness mechanism. Artifacts are for
167inter-job data. Cache may evict at any time - pipelines must work without it.
168169### Secret management
170171| Platform | Mechanism | Scope control |
172|----------|-----------|---------------|
173| **GitHub** | Repository/org/environment secrets | Per-environment, per-repo, per-org. Deployment branches. |
174| **GitLab** | CI/CD variables (project/group/instance) | Protected branches/tags, environments, masked in logs. |
175| **Forgejo** | Repository/org secrets | Per-repo, per-org. No environment scoping yet. |
176177**All platforms**: never echo secrets, never pass as CLI args (visible in `ps`), never write
178to artifacts. Use environment variables or file-based injection.
179180### Deployment gates
181182| Environment | Trigger | Approval |
183|-------------|---------|----------|
184| **Dev/Preview** | Every PR/MR push | None |
185| **Staging** | Merge to main | None (auto-deploy) |
186| **Production** | Tag or manual dispatch | Required reviewer(s) |
187188GitLab: `when: manual` + `environment:`. GitHub: `environment:` with protection rules.
189Forgejo: manual dispatch (`workflow_dispatch`).
190191## Monorepo Patterns
192193When a repo contains multiple services sharing a common library:
194195### Path-based triggering
196- **GitHub Actions**: `on.push.paths` / `on.pull_request.paths` to scope workflows per service
197- **GitLab CI/CD**: `rules: changes: paths:` with `compare_to: refs/heads/main`
198- **Forgejo**: same as GitHub Actions (`on.push.paths`)
199200### Shared library detection
201If `libs/common/` changes, rebuild all services that depend on it:
202- List dependent services in a matrix or trigger all service workflows
203- `paths` filters accept globs: `paths: ['services/api/**', 'libs/common/**']`
204205### Selective builds
206Build only what changed. Two approaches:
2071. **Per-service workflows** with `paths:` filters (simplest, recommended)
2082. **Single workflow with matrix** + change detection job that outputs which services need building
209210**Rule**: always rebuild when the shared lib changes. A "nothing changed" optimization that misses a shared dependency is worse than rebuilding everything.
211212### Python monorepo specifics (GitLab / GitHub / Forgejo)
213214For Python monorepos with multiple services sharing a common library (`libs/common/`):
215- **Cache the resolver output, not the install step.** Key on `hashFiles('**/requirements*.txt')` or `**/poetry.lock`/`**/uv.lock`. With `uv` or `pip`, cache `~/.cache/uv` or `~/.cache/pip` plus each service's `.venv/` keyed on the service path + lockfile hash.
216- **Install the shared lib editable** (`pip install -e libs/common`) so services import the in-repo version, not a stale wheel.
217- **Scope jobs per service with path filters.** GitLab: `rules: - changes: paths: ['services/api/**', 'libs/common/**'] compare_to: refs/heads/main`. GitHub/Forgejo: `on.push.paths` / `on.pull_request.paths`. Always include `libs/common/**` in every service's filter so a shared-lib change triggers all services.
218- **YAML anchors (GitLab) / reusable workflows (GitHub) for the per-service job template.** Three near-identical blocks for `api`, `worker`, `scheduler` is a maintenance trap.
219220See `references/gitlab-ci.md` for a full monorepo `.gitlab-ci.yml` (YAML anchors, `compare_to`, per-service change rules, shared-lib detection).
221222## Forgejo CI/CD
223224Forgejo Actions is "designed to be familiar, not designed to be compatible" with GitHub Actions.
225It reuses the workflow syntax but makes no compatibility guarantees.
226227### Key differences from GitHub Actions
228229| Feature | GitHub Actions | Forgejo Actions |
230|---------|---------------|-----------------|
231| **`permissions:`** | Controls GITHUB_TOKEN scope | **Not enforced** - token always has full rw (read-only for fork PRs) |
232| **`continue-on-error:`** (job level) | Allows job failure without failing workflow | **Not supported** - step-level only |
233| **Runner images** | Managed `ubuntu-24.04` with 200+ tools | Self-hosted, typically lean Debian/Alpine |
234| **Action resolution** | `actions/checkout@v4` -> github.com | Resolves from Forgejo mirror (configurable) |
235| **OIDC** | `permissions: id-token: write` | `enable-openid-connect` key |
236| **Workflow call defaults** | `inputs.<id>.default` populated | **Always empty** |
237| **Matrix + dynamic runs-on** | Supported | Supported since v14.0 |
238| **LXC execution** | Not supported | Supported (Forgejo-specific) |
239240### Forgejo workflow template
241242```yaml
243name: CI
244on:
245 push:
246 branches: [main]
247 pull_request:
248249jobs:
250 ci:
251 runs-on: docker # self-hosted runner label
252 container:
253 image: oven/bun:1.2 # pin to minor version minimum
254 steps:
255 - uses: actions/checkout@<sha> # pin to SHA; resolves from Forgejo mirror
256 - run: bun install --frozen-lockfile
257 - run: bun run lint
258 - run: bun run typecheck
259 - run: bun run test
260```
261262### Forgejo action SHA discovery
263264Forgejo resolves actions from its own mirror or a configured upstream, not from github.com.
265Finding the correct SHA for a self-hosted mirror requires different steps than GitHub.
266267**Find the SHA on your Forgejo instance**:
268```bash
269# List tags and their SHAs from the Forgejo mirror
270git ls-remote https://forgejo.example.com/actions/checkout.git 'refs/tags/v4*'
271272# Or use the Forgejo API to get a tag's commit SHA
273curl -s https://forgejo.example.com/api/v1/repos/actions/checkout/git/refs/tags/v4.2.2 \
274 | jq -r '.object.sha'
275```
276277**If your instance mirrors from code.forgejo.org** (the default upstream):
278```bash
279git ls-remote https://code.forgejo.org/actions/checkout.git 'refs/tags/v4*'
280```
281282**Verify a SHA matches what you expect**:
283```bash
284# Clone at the specific SHA and inspect
285git clone --depth 1 https://forgejo.example.com/actions/checkout.git /tmp/checkout-verify
286cd /tmp/checkout-verify
287git checkout <sha>
288# Review action.yml and dist/ - compare against the known-good upstream release
289```
290291**Key differences from GitHub SHA discovery**:
292- The same action (e.g., `actions/checkout`) may have different SHAs on Forgejo mirrors vs GitHub
293 because Forgejo forks maintain their own commits
294- `code.forgejo.org/actions/*` repos are Forgejo-maintained forks, not exact copies of GitHub repos
295- Always verify SHAs against your own instance, not against github.com
296- If the action repo is not mirrored yet, an admin must add it to the Forgejo mirror list
297298### Forgejo-specific gotchas
299300- **No `ubuntu-latest`** - `runs-on` maps to your registered runner labels (e.g., `docker`)
301- **Missing tools** - Forgejo runner containers are lean. Add `apt-get install` for git, curl, etc.
302- **TLS certs** - if Forgejo uses self-signed or internal CA certs, configure the runner's trust
303 store (`GIT_SSL_CAINFO=/path/to/ca-bundle.crt`) or install the CA into the container image.
304 `GIT_SSL_NO_VERIFY=true` is a last resort for dev/test only - never normalize TLS bypass in production
305- **Third-party actions** - many GitHub Marketplace actions use GitHub-specific API calls and will silently fail
306- **Secrets in Forgejo** - `${{ secrets.* }}` works, but no environment-level scoping
307- **`permissions:` not enforced** - Forgejo parses the field but does not restrict the workflow token.
308 The token always has full read-write access (read-only for fork PRs only). Don't assume
309 least-privilege from `permissions:` alone - it has no effect on Forgejo.
310311### Managing Forgejo Actions with `fj`
312313The community Forgejo CLI (`fj`, v0.4.1+) covers the day-to-day Actions surface: listing
314runs, dispatching workflows, and managing variables/secrets. It is much faster than the web
315UI for bulk secret updates and scriptable for one-shot runs. Install and auth details live
316in the **git** skill's `forge-workflows.md` reference.
317318```bash
319# List recent runs (for a quick "is CI green on main?" check)
320fj actions tasks
321322# Trigger a workflow_dispatch run without opening the browser
323fj actions dispatch publish.yaml main --inputs version=1.2.3
324325# Bulk variable/secret management (writes to the repo scope)
326fj actions variables create CACHE_BUCKET gs://my-bucket
327fj actions secrets create REGISTRY_TOKEN "$REGISTRY_TOKEN"
328```
329330**What `fj` does not do yet** (as of 0.4.1): stream runner logs, re-run failed jobs, cancel
331running tasks. For those, use the web UI or hit `/api/v1/repos/{owner}/{repo}/actions/tasks/{id}`
332directly. Log streaming across the fleet still belongs in your observability stack, not `fj`.
333334**On Gitea instead of Forgejo?** Use `tea` (`gitea.com/gitea/tea`) - the Gitea CLI covers
335a similar surface (issues, PRs, releases) against any Gitea 1.20+ instance. Gitea Actions
336lacks `fj`-equivalent CLI tooling; use the web UI or API. If you're running Forgejo,
337prefer `fj` - it tracks Forgejo-specific behavior (AGit, Forgejo Actions quirks) that
338`tea` does not.
339340### Gitea CI/CD
341342Gitea ships two viable CI paths: **Gitea Actions** (same `act`-based engine as Forgejo
343Actions, since Gitea 1.21) and **Woodpecker CI** (separate service, container-native,
344webhook-driven). Drone is legacy - do not start new installs.
345346Quick rule of thumb: if you are migrating from GitHub or want one service to operate,
347use Gitea Actions. If you need proper matrix builds, caching primitives, or lighter
348resource usage, use Woodpecker. Do not run both against the same repo.
349350See `references/forgejo-gitea-actions.md` for: action SHA discovery, Gitea-vs-Forgejo Actions
351differences, Woodpecker YAML examples, plugin vs command steps, OAuth setup, matrix
352patterns, and Drone migration guidance.
353354### Forgejo release workflow pattern
355356```yaml
357name: Release
358on:
359 push:
360 tags: ['v*']
361362jobs:
363 build-and-push:
364 runs-on: docker
365 container:
366 image: catthehacker/ubuntu:act-24.04 # heavier image for multi-tool needs
367 # Private-forge TLS: mount your CA and set GIT_SSL_CAINFO=/path/to/ca.crt.
368 # GIT_SSL_NO_VERIFY is a dev/test-only last resort - never commit it to a release pipeline.
369 steps:
370 - uses: actions/checkout@<sha> # pin to SHA; resolves from Forgejo mirror
371 - name: Login to registry
372 env:
373 TOKEN: ${{ secrets.REGISTRY_TOKEN }}
374 HOST: ${{ secrets.REGISTRY_HOST }}
375 USER: ${{ secrets.REGISTRY_USER }}
376 run: echo "$TOKEN" | docker login "$HOST" -u "$USER" --password-stdin
377 - name: Build and push
378 env:
379 REGISTRY: ${{ secrets.REGISTRY_HOST }}/${{ secrets.REGISTRY_IMAGE }}
380 TAG: ${{ github.ref_name }}
381 run: |
382 docker build -t "$REGISTRY:$TAG" .
383 docker push "$REGISTRY:$TAG"
384```
385386**Note**: use secrets for registry host/image to avoid hardcoding private domains in git history.
387388## PCI-DSS 4.0: CI/CD Compliance Mapping
389390All future-dated requirements became **mandatory March 31, 2025**.
391392| PCI-DSS Req | What it means for CI/CD | Implementation |
393|-------------|-------------------------|----------------|
394| **6.2.1** | Secure development training + OWASP-aware processes | SAST on every PR/MR, dependency scanning, secret detection |
395| **6.2.4** | Access control + change tracking | Branch protection, required reviewers, signed commits, audit logs |
396| **6.3.2** | Software inventory (SBOM) | Generate SPDX/CycloneDX SBOM per release, attach to artifacts |
397| **6.4.2** | Changes approved, documented, tested | Gated deployments, required approvals for prod, IaC audit trails |
398| **6.5.3** | Consistent security controls across environments | Same scanning in dev/staging/prod, not just prod |
399400**Customized Approach** (v4.0.1): automated CI/CD controls can satisfy manual review requirements
401if properly documented. An automated SAST/DAST/SCA gate with evidence = equivalent to manual
402code review for QSA assessment.
403404Read `references/supply-chain.md` for detailed PCI-DSS compliance patterns.
405406## AI-Age Considerations
407408AI tools consistently generate insecure CI/CD configs: unpinned actions, missing `permissions:`
409blocks, `allow_failure: true` without justification, `:latest` tags, secrets in `run:` blocks.
410**Always run the AI Self-Check against AI-generated pipeline code.**
411412For detailed coverage of slopsquatting, AI agents in CI/CD, prompt injection in pipelines, and
413the OWASP Top 10 for Agentic Applications, read `references/supply-chain.md`
414(AI-Age Supply Chain Risks section).
415416## Template Conventions
417418- **`@<sha>`** in GitHub Actions templates is a placeholder. Replace with the real 40-character
419 commit SHA for the indicated version. Look up SHAs on the action's releases page or use
420 Dependabot to manage them automatically.
421- **Image tags** in templates use floating minor versions (e.g., `oven/bun:1.2`, `docker:27.5`)
422 for readability. For production, pin to a specific patch version or SHA256 digest. The templates
423 show the minimum acceptable granularity, not the ideal.
424425## Reference Files
426427- `references/github-actions.md` - GitHub Actions patterns, templates, and security hardening
428- `references/forgejo-gitea-actions.md` - Forgejo/Gitea Actions differences, troubleshooting, Woodpecker patterns, and Drone migration guidance
429- `references/gitlab-ci.md` - GitLab CI/CD 19.x patterns, SaaS vs self-managed differences, Catalog, Components, security
430- `references/runners.md` - Self-hosted runners (actions-runner, gitlab-runner, forgejo-runner, act_runner, woodpecker-agent) - install, register, executor choice, Linux vs macOS, security hardening
431- `references/best-practices.md` - Dependency updates (Dependabot/Renovate), layered linting, scanning matrix (secrets/SCA/container/IaC/SAST), review gates, merge queues, rollout order
432- `references/supply-chain.md` - supply chain security, incident timeline, SHA pinning, SBOM/SLSA, PCI-DSS compliance, image signing
433- `references/target-versions.md` - May 2026 version snapshot for forges, runners, CI systems, and supply-chain tools
434435## Output Contract
436437See `skills/_shared/output-contract.md` for the full contract.
438439- **Skill name:** CI-CD
440- **Deliverable bucket:** `audits`
441- **Mode:** conditional. When invoked to **analyze, review, audit, or improve** existing repo content, emit the full contract - boxed inline header, body summary inline plus per-finding detail in the deliverable file, boxed conclusion, conclusion table - and write the deliverable to `docs/local/audits/ci-cd/<YYYY-MM-DD>-<slug>.md`. When invoked to **answer a question, teach a concept, build a new artifact, or generate content**, respond freely without the contract.
442- **Severity scale:** `P0 | P1 | P2 | P3 | info` (see shared contract; only used in audit/review mode).
443444## Related Skills
445446- **code-review** - has a `cicd-pipelines.md` reference for CI/CD **bug patterns** (expression
447 injection, variable scoping, cache gotchas, ArgoCD sync issues)
448- **security-audit** - for auditing application code, not pipeline code
449- **docker** - for Dockerfile and container image optimization
450- **kubernetes** - for K8s manifests and Helm charts that pipelines deploy to
451- **git** - for git operations (commits, PRs/MRs, tags, releases) that trigger pipelines.
452 CI/CD reacts to git events; git handles the operations that produce them.
453454## Rules
455456- **Platform-first.** Always confirm which CI/CD platform before writing config. GitHub Actions
457 syntax that "mostly works" in Forgejo will silently break on edge cases.
458- **SHA-pin everything.** All third-party actions, all CI tool images. Tags are mutable. SHAs are not.
459 The tj-actions, reviewdog, and Trivy compromises proved this is non-negotiable.
460- **Secrets are sacred.** Never log, echo, artifact, or pass as CLI arguments. Never use
461 protected variables on unprotected branches.
462- **Test the pipeline itself.** `act` (GitHub Actions local runner), `gitlab-ci-local`, or dry-run
463 modes. Don't discover pipeline bugs in production.
464- **Cache != artifact.** Cache is ephemeral speed optimization. Artifacts are guaranteed inter-job
465 data. Confusing them causes intermittent failures.
466- **Manual gates for prod.** No exceptions. Auto-deploy to staging is fine. Auto-deploy to
467 production is how incidents happen.
468- **Scan early, deploy late.** Security scanning in the first stages, deployment in the last.
469 Finding a CVE after deployment is expensive.
470- **PCI-DSS 4.0 is mandatory.** If the pipeline touches CDE (cardholder data environment),
471 SBOM generation, signed artifacts, and gated deployments are not optional.
Run npx skillmds add majiayu000/ci-cd-8 in your terminal (requires Node.js), paste this page's agent-chat prompt into Claude, Cursor, or any MCP-connected agent, or download the SKILL.md file and copy it into your agent's skills directory.
· Write/review CI/CD for GitHub Actions, GitLab, Forgejo/Gitea, Woodpecker. Triggers: 'ci/cd', 'pipeline', 'github actions', 'gitlab ci', 'runner', 'renovate', 'trivy'. Not for git workflows (use git). It is listed under DevOps & Infra on SkillMD.
This skill has not completed SkillMD's automated safety review yet. Capability flags: makes network calls, reads secrets. SkillMD never runs a skill's scripts for you; review the SKILL.md before installing.
This skill is tagged as working with Claude Code, Claude.ai, OpenAI Codex. SKILL.md is an open format, so most agents that read a skills directory can load it too.
Yes. Installing skills from SkillMD is free. This skill is licensed under MIT.
majiayu000 (@majiayu000) published this skill. Their other Agent Skills are listed on their SkillMD profile.