CI/CD pipeline patterns with GitHub Actions: build/test/lint workflows, environment gates, caching, security scanning, Docker builds, and deployment automation. Includes templates for TypeScript, Python, Go, and Java.
From clarcnpx claudepluginhub marvinrichter/clarc --plugin clarcThis skill uses the workspace's default tool permissions.
Designs and optimizes AI agent action spaces, tool definitions, observation formats, error recovery, and context for higher task completion rates.
Enables AI agents to execute x402 payments with per-task budgets, spending controls, and non-custodial wallets via MCP tools. Use when agents pay for APIs, services, or other agents.
Compares coding agents like Claude Code and Aider on custom YAML-defined codebase tasks using git worktrees, measuring pass rate, cost, time, and consistency.
Reliable software delivery requires automated pipelines that catch problems before they reach users. Every merge to main should run through the same quality gates that Claude Code runs locally.
/setup-ci)@startuml
start
fork
:Lint\n(fast, < 1min);
fork again
:Type Check\n(fast, < 2min);
fork again
:Unit Tests\n(parallel shards);
end fork
:Integration Tests\n(needs services: DB, Redis);
if (Branch = main?) then (yes)
:Build Docker Image\npush to registry;
:Deploy → Staging;
:Smoke Tests on Staging;
if (Manual approval?) then (yes)
:Deploy → Production;
:Notify team;
endif
else (PR / feature branch)
:Report results\nto PR;
stop
endif
stop
@enduml
# .github/workflows/ci.yml
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true # Cancel superseded runs on same PR
jobs:
lint:
name: Lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '24'
cache: 'npm'
- run: npm ci
- run: npm run lint
typecheck:
name: Type Check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: '24', cache: 'npm' }
- run: npm ci
- run: npm run typecheck
test:
name: Test
runs-on: ubuntu-latest
services:
postgres:
image: postgres:18-alpine
env:
POSTGRES_DB: testdb
POSTGRES_USER: test
POSTGRES_PASSWORD: test
ports: ['5432:5432']
options: --health-cmd pg_isready --health-interval 5s --health-timeout 5s --health-retries 5
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: '24', cache: 'npm' }
- run: npm ci
- run: npm test -- --coverage
env:
DATABASE_URL: postgresql://test:test@localhost:5432/testdb
- uses: codecov/codecov-action@v4 # optional: coverage reporting
Add this job to every PR that touches api/v1/openapi.yaml:
api-breaking-changes:
name: API Breaking Change Detection
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with: { fetch-depth: 0 }
- name: Install oasdiff
run: |
curl -fsSL https://raw.githubusercontent.com/tufin/oasdiff/main/install.sh | sh
- name: Check for breaking changes
run: |
oasdiff breaking \
https://raw.githubusercontent.com/${{ github.repository }}/main/api/v1/openapi.yaml \
api/v1/openapi.yaml \
--fail-on ERR
docker:
name: Build & Push Docker Image
runs-on: ubuntu-latest
needs: [lint, typecheck, test]
if: github.ref == 'refs/heads/main'
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- 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:
push: true
tags: |
ghcr.io/${{ github.repository }}:latest
ghcr.io/${{ github.repository }}:${{ github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max
# .github/workflows/deploy.yml
name: Deploy
on:
workflow_run:
workflows: [CI]
branches: [main]
types: [completed]
jobs:
deploy-staging:
if: ${{ github.event.workflow_run.conclusion == 'success' }}
environment: staging # requires environment to be set up in GitHub settings
runs-on: ubuntu-latest
steps:
- run: echo "Deploying to staging..."
# Add actual deploy steps here (kubectl, fly deploy, railway up, etc.)
smoke-test:
needs: deploy-staging
runs-on: ubuntu-latest
steps:
- run: |
curl --fail https://staging.yourapp.com/health/ready || exit 1
deploy-production:
needs: smoke-test
environment:
name: production
url: https://yourapp.com
runs-on: ubuntu-latest
steps:
- run: echo "Deploying to production..."
Set up environment protection rules in GitHub → Settings → Environments:
staging: no protection (auto-deploy)production: require 1 reviewer approval security:
name: Security Scan
runs-on: ubuntu-latest
permissions:
security-events: write
steps:
- uses: actions/checkout@v4
# Dependency vulnerabilities
- name: Run npm audit
run: npm audit --audit-level=high
continue-on-error: true # report but don't block
# SAST (Static Application Security Testing)
- uses: github/codeql-action/init@v3
with:
languages: javascript # or: python, go, java
- uses: github/codeql-action/analyze@v3
# Secret scanning (prevent credentials in code)
- uses: trufflesecurity/trufflehog@main
with:
path: ./
base: ${{ github.event.repository.default_branch }}
head: HEAD
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v4
with: { enable-cache: true }
- run: uv sync --frozen
- run: uv run ruff check .
- run: uv run mypy .
- run: uv run pytest --cov --cov-report=xml
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with: { go-version-file: go.mod, cache: true }
- run: go vet ./...
- run: go test ./... -race -coverprofile=coverage.out
- name: golangci-lint
uses: golangci/golangci-lint-action@v6
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with: { java-version: '25', distribution: 'temurin', cache: 'maven' }
- run: mvn verify
| Language | Cache Key | What to Cache |
|---|---|---|
| Node.js | package-lock.json hash | ~/.npm |
| Python (uv) | uv.lock hash | ~/.cache/uv |
| Go | go.sum hash | ~/.cache/go-build, ~/go/pkg/mod |
| Java (Maven) | pom.xml hash | ~/.m2 |
| Docker | Dockerfile + source hash | GitHub Actions cache (type=gha) |
Always restore cache before install, save after. Use actions/cache@v4 or the setup actions' built-in caching.
# NEVER hardcode secrets in workflows
# Set them in GitHub → Settings → Secrets and Variables → Actions
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }}
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
# For environment-specific secrets:
API_KEY: ${{ secrets.STAGING_API_KEY }} # in staging environment
API_KEY: ${{ secrets.PRODUCTION_API_KEY }} # in production environment
Rules:
production environment (with required reviewers)echo $SECRET → shows in logs)Before going live:
latest)concurrency group set (cancel superseded runs on PRs)