Skip to content

CI/CD Pipeline Patterns

Master continuous integration and deployment with Cursor IDE and Claude Code. This guide covers GitHub Actions, GitLab CI, Jenkins pipelines, automated testing, security scanning, and production deployment patterns with AI assistance.

  1. Initialize CI/CD Pipeline

    Terminal window
    # Generate CI/CD configuration
    Agent: "Create CI/CD pipeline with:
    - Build and test stages
    - Security scanning
    - Multi-environment deployment
    - Automated rollback
    - Notification integration"
  2. Install CI/CD MCP Servers (optional)

    Terminal window
    # CircleCI
    claude mcp add circleci -- npx -y @circleci/mcp-server-circleci
    # Jenkins
    claude mcp add jenkins -- npx -y jenkins-mcp-server
    # GitHub (for Actions integration)
    claude mcp add --transport sse github https://api.githubcopilot.com/mcp/
  3. Configure AI Rules

    # .cursorrules or CLAUDE.md
    CI/CD best practices:
    - Use semantic versioning
    - Implement branch protection
    - Run tests in parallel
    - Cache dependencies
    - Use environment-specific configs
    - Follow security best practices
    - Implement proper secret management
# AI Prompt
Agent: "Create GitHub Actions workflow with:
- Matrix builds for multiple versions
- Dependency caching
- Security scanning
- Docker build and push
- Deployment to staging/production
- Rollback capability"
# .github/workflows/main.yml
name: CI/CD Pipeline
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
release:
types: [published]
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
NODE_VERSION: '20.x'
jobs:
# Code Quality and Security
quality:
name: Code Quality Checks
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0 # Full history for better analysis
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run linting
run: npm run lint
- name: Run type checking
run: npm run type-check
- name: Check code formatting
run: npm run format:check
- name: Run security audit
run: npm audit --audit-level=moderate
- name: SonarCloud Scan
uses: SonarSource/sonarcloud-github-action@master
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
# Testing
test:
name: Test Suite
runs-on: ubuntu-latest
needs: quality
strategy:
matrix:
node-version: [18.x, 20.x, 22.x]
test-suite: [unit, integration, e2e]
services:
postgres:
image: postgres:16
env:
POSTGRES_PASSWORD: postgres
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 5432:5432
redis:
image: redis:7-alpine
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 6379:6379
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run migrations
run: npm run db:migrate
env:
DATABASE_URL: postgresql://postgres:postgres@localhost:5432/test_db
- name: Run ${{ matrix.test-suite }} tests
run: npm run test:${{ matrix.test-suite }}
env:
DATABASE_URL: postgresql://postgres:postgres@localhost:5432/test_db
REDIS_URL: redis://localhost:6379
CI: true
- name: Upload coverage
if: matrix.test-suite == 'unit' && matrix.node-version == '20.x'
uses: codecov/codecov-action@v4
with:
file: ./coverage/lcov.info
flags: unittests
# Build and Push Docker Image
build:
name: Build Docker Image
runs-on: ubuntu-latest
needs: test
permissions:
contents: read
packages: write
security-events: write
outputs:
image: ${{ steps.image.outputs.image }}
digest: ${{ steps.build.outputs.digest }}
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=sha
- name: Build and push Docker image
id: build
uses: docker/build-push-action@v5
with:
context: .
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
build-args: |
VERSION=${{ github.sha }}
BUILD_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
- name: Generate SBOM
uses: anchore/sbom-action@v0
with:
image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@${{ steps.build.outputs.digest }}
format: spdx-json
output-file: sbom.spdx.json
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@${{ steps.build.outputs.digest }}
format: 'sarif'
output: 'trivy-results.sarif'
severity: 'CRITICAL,HIGH'
- name: Upload Trivy scan results
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: 'trivy-results.sarif'
- name: Output image
id: image
run: echo "image=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@${{ steps.build.outputs.digest }}" >> $GITHUB_OUTPUT
# Deploy to Staging
deploy-staging:
name: Deploy to Staging
runs-on: ubuntu-latest
needs: build
if: github.ref == 'refs/heads/develop'
environment: staging
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
aws-region: us-east-1
- name: Update kubeconfig
run: |
aws eks update-kubeconfig --name staging-cluster --region us-east-1
- name: Deploy to Kubernetes
run: |
kubectl set image deployment/app app=${{ needs.build.outputs.image }} -n staging
kubectl rollout status deployment/app -n staging
- name: Run smoke tests
run: |
./scripts/smoke-test.sh https://staging.example.com
- name: Notify Slack
if: always()
uses: 8398a7/action-slack@v3
with:
status: ${{ job.status }}
webhook_url: ${{ secrets.SLACK_WEBHOOK }}
channel: '#deployments'
text: 'Staging deployment ${{ job.status }}'
# Deploy to Production
deploy-production:
name: Deploy to Production
runs-on: ubuntu-latest
needs: [build, deploy-staging]
if: github.event_name == 'release'
environment: production
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.AWS_PROD_ROLE_ARN }}
aws-region: us-east-1
- name: Update kubeconfig
run: |
aws eks update-kubeconfig --name production-cluster --region us-east-1
- name: Create deployment record
id: deployment
uses: actions/github-script@v7
with:
script: |
const deployment = await github.rest.repos.createDeployment({
owner: context.repo.owner,
repo: context.repo.repo,
ref: context.sha,
environment: 'production',
required_contexts: [],
production_environment: true,
auto_merge: false
});
return deployment.data.id;
- name: Blue-Green Deployment
run: |
# Deploy to green environment
kubectl set image deployment/app-green app=${{ needs.build.outputs.image }} -n production
kubectl rollout status deployment/app-green -n production
# Run health checks
./scripts/health-check.sh https://green.example.com
# Switch traffic
kubectl patch service app-service -n production -p '{"spec":{"selector":{"version":"green"}}}'
# Wait and verify
sleep 30
./scripts/verify-deployment.sh https://example.com
- name: Update deployment status
if: always()
uses: actions/github-script@v7
with:
script: |
await github.rest.repos.createDeploymentStatus({
owner: context.repo.owner,
repo: context.repo.repo,
deployment_id: ${{ steps.deployment.outputs.result }},
state: '${{ job.status }}',
environment_url: 'https://example.com'
});
- name: Create release notes
uses: actions/github-script@v7
with:
script: |
const release = await github.rest.repos.getReleaseByTag({
owner: context.repo.owner,
repo: context.repo.repo,
tag: context.ref.replace('refs/tags/', '')
});
const newBody = release.data.body + '\n\n## Deployment\n' +
`- Image: \`${{ needs.build.outputs.image }}\`\n` +
`- Deployed at: ${new Date().toISOString()}\n` +
`- Deployed by: @${context.actor}`;
await github.rest.repos.updateRelease({
owner: context.repo.owner,
repo: context.repo.repo,
release_id: release.data.id,
body: newBody
});
.github/workflows/reusable-node-ci.yml
# AI Prompt: "Create reusable workflow for Node.js projects"
name: Reusable Node.js CI
on:
workflow_call:
inputs:
node-version:
required: false
type: string
default: '20.x'
run-e2e:
required: false
type: boolean
default: false
deploy-environment:
required: false
type: string
secrets:
NPM_TOKEN:
required: false
SONAR_TOKEN:
required: false
jobs:
ci:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ inputs.node-version }}
cache: 'npm'
registry-url: 'https://registry.npmjs.org'
- name: Install dependencies
run: npm ci
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Run tests
run: |
npm run test:unit
npm run test:integration
${{ inputs.run-e2e && 'npm run test:e2e' || '' }}
- name: Build
run: npm run build
- name: SonarCloud Scan
if: secrets.SONAR_TOKEN != ''
uses: SonarSource/sonarcloud-github-action@master
env:
GITHUB_TOKEN: ${{ github.token }}
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
# Usage in main workflow
jobs:
test:
uses: ./.github/workflows/reusable-node-ci.yml
with:
node-version: '20.x'
run-e2e: true
secrets: inherit
# AI Prompt
Agent: "Create GitLab CI pipeline with:
- Multi-stage pipeline
- Docker in Docker
- Parallel testing
- Security scanning
- Review apps
- Auto DevOps integration"
# .gitlab-ci.yml
variables:
DOCKER_DRIVER: overlay2
DOCKER_TLS_CERTDIR: "/certs"
CONTAINER_TEST_IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG
CONTAINER_RELEASE_IMAGE: $CI_REGISTRY_IMAGE:latest
POSTGRES_DB: test_db
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_HOST_AUTH_METHOD: trust
stages:
- build
- test
- security
- deploy
- cleanup
# Templates
.docker:
image: docker:24
services:
- docker:24-dind
before_script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
.deploy:
image: bitnami/kubectl:latest
before_script:
- kubectl config set-cluster k8s --server="$KUBE_URL" --insecure-skip-tls-verify=true
- kubectl config set-credentials admin --token="$KUBE_TOKEN"
- kubectl config set-context default --cluster=k8s --user=admin
- kubectl config use-context default
# Build Stage
build:
extends: .docker
stage: build
script:
- docker build --pull -t $CONTAINER_TEST_IMAGE .
- docker push $CONTAINER_TEST_IMAGE
- |
if [[ "$CI_COMMIT_BRANCH" == "$CI_DEFAULT_BRANCH" ]]; then
docker tag $CONTAINER_TEST_IMAGE $CONTAINER_RELEASE_IMAGE
docker push $CONTAINER_RELEASE_IMAGE
fi
rules:
- if: $CI_COMMIT_BRANCH
- if: $CI_MERGE_REQUEST_IID
# Test Stage
test:unit:
stage: test
image: node:20-alpine
services:
- postgres:16-alpine
- redis:7-alpine
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- node_modules/
- .npm/
before_script:
- npm ci --cache .npm --prefer-offline
script:
- npm run test:unit -- --coverage
coverage: '/Lines\s*:\s*(\d+\.\d+)%/'
artifacts:
reports:
coverage_report:
coverage_format: cobertura
path: coverage/cobertura-coverage.xml
junit: junit.xml
paths:
- coverage/
expire_in: 1 week
parallel:
matrix:
- NODE_VERSION: ["18", "20", "22"]
test:integration:
stage: test
image: $CONTAINER_TEST_IMAGE
services:
- postgres:16-alpine
- redis:7-alpine
script:
- npm run test:integration
needs: ["build"]
test:e2e:
stage: test
image: mcr.microsoft.com/playwright:focal
services:
- name: $CONTAINER_TEST_IMAGE
alias: app
variables:
APP_URL: http://app:3000
script:
- npm ci
- npx playwright install
- npm run test:e2e
artifacts:
when: always
paths:
- playwright-report/
- test-results/
expire_in: 1 week
needs: ["build"]
# Security Stage
security:sast:
stage: security
include:
- template: Security/SAST.gitlab-ci.yml
security:dependency:
stage: security
include:
- template: Security/Dependency-Scanning.gitlab-ci.yml
security:container:
extends: .docker
stage: security
script:
- docker run --rm -v /var/run/docker.sock:/var/run/docker.sock
aquasec/trivy image --severity HIGH,CRITICAL
--exit-code 1 $CONTAINER_TEST_IMAGE
needs: ["build"]
allow_failure: true
security:license:
stage: security
include:
- template: Security/License-Scanning.gitlab-ci.yml
# Deploy Stage
deploy:review:
extends: .deploy
stage: deploy
script:
- kubectl create namespace review-$CI_MERGE_REQUEST_IID || true
- |
cat <<EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
name: app
namespace: review-$CI_MERGE_REQUEST_IID
spec:
replicas: 1
selector:
matchLabels:
app: review-app
template:
metadata:
labels:
app: review-app
spec:
containers:
- name: app
image: $CONTAINER_TEST_IMAGE
ports:
- containerPort: 3000
env:
- name: NODE_ENV
value: review
---
apiVersion: v1
kind: Service
metadata:
name: app-service
namespace: review-$CI_MERGE_REQUEST_IID
spec:
selector:
app: review-app
ports:
- port: 80
targetPort: 3000
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: app-ingress
namespace: review-$CI_MERGE_REQUEST_IID
spec:
rules:
- host: review-$CI_MERGE_REQUEST_IID.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: app-service
port:
number: 80
EOF
environment:
name: review/$CI_MERGE_REQUEST_IID
url: https://review-$CI_MERGE_REQUEST_IID.example.com
on_stop: stop:review
auto_stop_in: 1 week
rules:
- if: $CI_MERGE_REQUEST_IID
needs: ["build", "test:unit", "test:integration"]
stop:review:
extends: .deploy
stage: cleanup
script:
- kubectl delete namespace review-$CI_MERGE_REQUEST_IID
environment:
name: review/$CI_MERGE_REQUEST_IID
action: stop
rules:
- if: $CI_MERGE_REQUEST_IID
when: manual
deploy:staging:
extends: .deploy
stage: deploy
script:
- kubectl set image deployment/app app=$CONTAINER_TEST_IMAGE -n staging
- kubectl rollout status deployment/app -n staging
environment:
name: staging
url: https://staging.example.com
rules:
- if: $CI_COMMIT_BRANCH == "develop"
needs: ["build", "test:unit", "test:integration", "security:container"]
deploy:production:
extends: .deploy
stage: deploy
script:
- kubectl set image deployment/app app=$CONTAINER_RELEASE_IMAGE -n production
- kubectl rollout status deployment/app -n production
environment:
name: production
url: https://example.com
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
when: manual
needs: ["build", "test:unit", "test:integration", "security:container"]
# Performance Testing
performance:
stage: test
image: loadimpact/k6:latest
script:
- k6 run tests/performance/load-test.js
artifacts:
reports:
performance: performance.json
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
// AI Prompt
Agent: "Create Jenkins pipeline with:
- Parallel stages
- Docker agents
- Shared libraries
- Blue Ocean compatible
- Slack notifications"
// Jenkinsfile
@Library('shared-library@main') _
pipeline {
agent none
options {
buildDiscarder(logRotator(numToKeepStr: '10'))
timeout(time: 1, unit: 'HOURS')
timestamps()
ansiColor('xterm')
parallelsAlwaysFailFast()
}
environment {
DOCKER_REGISTRY = 'registry.example.com'
DOCKER_IMAGE = "${DOCKER_REGISTRY}/app"
SLACK_CHANNEL = '#deployments'
SONAR_HOST = credentials('sonar-host')
SONAR_TOKEN = credentials('sonar-token')
}
stages {
stage('Checkout') {
agent any
steps {
checkout scm
script {
env.GIT_COMMIT_SHORT = sh(
script: "git rev-parse --short HEAD",
returnStdout: true
).trim()
env.VERSION = "${BUILD_NUMBER}-${GIT_COMMIT_SHORT}"
}
}
}
stage('Quality Gates') {
parallel {
stage('Linting') {
agent {
docker {
image 'node:20-alpine'
args '-v $HOME/.npm:/root/.npm'
}
}
steps {
sh 'npm ci'
sh 'npm run lint'
}
}
stage('Security Scan') {
agent any
steps {
sh 'npm audit --audit-level=moderate'
dependencyCheck additionalArguments: '--scan .',
odcInstallation: 'dependency-check'
publishHTML([
allowMissing: false,
alwaysLinkToLastBuild: true,
keepAll: true,
reportDir: 'dependency-check-report',
reportFiles: 'dependency-check-report.html',
reportName: 'Dependency Check Report'
])
}
}
stage('SonarQube Analysis') {
agent any
steps {
withSonarQubeEnv('SonarQube') {
sh """
sonar-scanner \
-Dsonar.projectKey=my-app \
-Dsonar.sources=. \
-Dsonar.host.url=$SONAR_HOST \
-Dsonar.login=$SONAR_TOKEN
"""
}
timeout(time: 10, unit: 'MINUTES') {
waitForQualityGate abortPipeline: true
}
}
}
}
}
stage('Test') {
matrix {
axes {
axis {
name 'NODE_VERSION'
values '18', '20', '22'
}
axis {
name 'TEST_SUITE'
values 'unit', 'integration', 'e2e'
}
}
excludes {
exclude {
axis {
name 'NODE_VERSION'
values '18', '22'
}
axis {
name 'TEST_SUITE'
values 'e2e'
}
}
}
agent {
docker {
image "node:${NODE_VERSION}-alpine"
args '-v $HOME/.npm:/root/.npm'
}
}
stages {
stage('Run Tests') {
steps {
sh 'npm ci'
sh "npm run test:${TEST_SUITE}"
}
post {
always {
junit '**/test-results/*.xml'
publishHTML([
allowMissing: false,
alwaysLinkToLastBuild: true,
keepAll: true,
reportDir: 'coverage',
reportFiles: 'index.html',
reportName: "Coverage Report - ${NODE_VERSION} - ${TEST_SUITE}"
])
}
}
}
}
}
}
stage('Build Docker Image') {
agent any
steps {
script {
docker.withRegistry("https://${DOCKER_REGISTRY}", 'docker-credentials') {
def customImage = docker.build(
"${DOCKER_IMAGE}:${VERSION}",
"--build-arg VERSION=${VERSION} ."
)
customImage.push()
customImage.push('latest')
}
}
}
}
stage('Deploy') {
parallel {
stage('Deploy to Staging') {
when {
branch 'develop'
}
agent any
steps {
deployToKubernetes(
environment: 'staging',
image: "${DOCKER_IMAGE}:${VERSION}",
namespace: 'staging'
)
}
post {
success {
runSmokeTests('https://staging.example.com')
}
}
}
stage('Deploy to Production') {
when {
branch 'main'
}
agent any
input {
message 'Deploy to production?'
ok 'Deploy'
submitter 'admin,lead-dev'
parameters {
choice(
name: 'DEPLOYMENT_TYPE',
choices: ['Blue-Green', 'Canary', 'Rolling'],
description: 'Select deployment strategy'
)
}
}
steps {
script {
switch(params.DEPLOYMENT_TYPE) {
case 'Blue-Green':
blueGreenDeploy(
image: "${DOCKER_IMAGE}:${VERSION}",
namespace: 'production'
)
break
case 'Canary':
canaryDeploy(
image: "${DOCKER_IMAGE}:${VERSION}",
namespace: 'production',
percentage: 10
)
break
default:
deployToKubernetes(
environment: 'production',
image: "${DOCKER_IMAGE}:${VERSION}",
namespace: 'production'
)
}
}
}
}
}
}
}
post {
always {
node('any') {
cleanWs()
}
}
success {
slackSend(
channel: env.SLACK_CHANNEL,
color: 'good',
message: "✅ Build Successful: ${env.JOB_NAME} #${env.BUILD_NUMBER} (<${env.BUILD_URL}|Open>)"
)
}
failure {
slackSend(
channel: env.SLACK_CHANNEL,
color: 'danger',
message: "❌ Build Failed: ${env.JOB_NAME} #${env.BUILD_NUMBER} (<${env.BUILD_URL}|Open>)"
)
}
unstable {
slackSend(
channel: env.SLACK_CHANNEL,
color: 'warning',
message: "⚠️ Build Unstable: ${env.JOB_NAME} #${env.BUILD_NUMBER} (<${env.BUILD_URL}|Open>)"
)
}
}
}
# AI Prompt: "Integrate security scanning throughout CI/CD"
# security-scan.yml (GitHub Actions)
name: Security Scanning
on:
pull_request:
push:
branches: [main, develop]
schedule:
- cron: '0 0 * * *' # Daily scan
jobs:
# Static Application Security Testing (SAST)
sast:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run Semgrep
uses: returntocorp/semgrep-action@v1
with:
config: >-
p/security-audit
p/owasp-top-ten
p/r2c-best-practices
- name: Run CodeQL Analysis
uses: github/codeql-action/analyze@v3
with:
languages: javascript, typescript
- name: ESLint Security Plugin
run: |
npm ci
npx eslint . --ext .js,.ts --plugin security
# Software Composition Analysis (SCA)
sca:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run Snyk
uses: snyk/actions/node@master
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
with:
args: --severity-threshold=high
- name: OWASP Dependency Check
uses: dependency-check/Dependency-Check_Action@main
with:
project: 'my-app'
path: '.'
format: 'HTML'
- name: License Compliance
run: |
npm ci
npx license-checker --production --summary
# Container Security
container-security:
runs-on: ubuntu-latest
if: github.event_name == 'push'
steps:
- uses: actions/checkout@v4
- name: Build Image
run: docker build -t security-scan:${{ github.sha }} .
- name: Run Trivy
uses: aquasecurity/trivy-action@master
with:
image-ref: security-scan:${{ github.sha }}
format: 'table'
exit-code: '1'
ignore-unfixed: true
vuln-type: 'os,library'
severity: 'CRITICAL,HIGH'
- name: Run Grype
uses: anchore/scan-action@v3
with:
image: security-scan:${{ github.sha }}
fail-build: true
severity-cutoff: high
# Infrastructure as Code Security
iac-security:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Checkov Scan
uses: bridgecrewio/checkov-action@master
with:
directory: .
framework: all
output_format: sarif
output_file_path: reports/checkov.sarif
- name: Terraform Security Scan
uses: triat/terraform-security-scan@v3
with:
tfsec_actions_comment: true
tfsec_actions_fmt_comment: true
# Secrets Detection
secrets:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Detect Secrets
uses: trufflesecurity/trufflehog@main
with:
path: ./
base: ${{ github.event.repository.default_branch }}
head: HEAD
- name: GitLeaks
uses: gitleaks/gitleaks-action@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# AI Prompt
Agent: "Create progressive deployment pipeline with:
- Feature flags
- Canary releases
- A/B testing
- Rollback automation
- Monitoring integration"
# deployment-strategy.yml
name: Progressive Deployment
on:
workflow_dispatch:
inputs:
environment:
description: 'Target environment'
required: true
type: choice
options:
- staging
- production
strategy:
description: 'Deployment strategy'
required: true
type: choice
options:
- rolling
- blue-green
- canary
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup deployment
id: setup
run: |
echo "environment=${{ inputs.environment }}" >> $GITHUB_OUTPUT
echo "namespace=${{ inputs.environment }}" >> $GITHUB_OUTPUT
echo "strategy=${{ inputs.strategy }}" >> $GITHUB_OUTPUT
- name: Configure kubectl
uses: azure/setup-kubectl@v3
- name: Rolling Update
if: inputs.strategy == 'rolling'
run: |
kubectl set image deployment/app \
app=${{ env.IMAGE }}:${{ github.sha }} \
-n ${{ steps.setup.outputs.namespace }}
kubectl rollout status deployment/app \
-n ${{ steps.setup.outputs.namespace }}
- name: Blue-Green Deployment
if: inputs.strategy == 'blue-green'
run: |
# Deploy to inactive environment
ACTIVE=$(kubectl get service app-service -n ${{ steps.setup.outputs.namespace }} \
-o jsonpath='{.spec.selector.color}')
INACTIVE=$([[ "$ACTIVE" == "blue" ]] && echo "green" || echo "blue")
kubectl set image deployment/app-$INACTIVE \
app=${{ env.IMAGE }}:${{ github.sha }} \
-n ${{ steps.setup.outputs.namespace }}
kubectl rollout status deployment/app-$INACTIVE \
-n ${{ steps.setup.outputs.namespace }}
# Run health checks
./scripts/health-check.sh $INACTIVE
# Switch traffic
kubectl patch service app-service \
-n ${{ steps.setup.outputs.namespace }} \
-p '{"spec":{"selector":{"color":"'$INACTIVE'"}}}'
- name: Canary Deployment
if: inputs.strategy == 'canary'
run: |
# Deploy canary
kubectl apply -f - <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
name: app-canary
namespace: ${{ steps.setup.outputs.namespace }}
spec:
replicas: 1
selector:
matchLabels:
app: app
version: canary
template:
metadata:
labels:
app: app
version: canary
spec:
containers:
- name: app
image: ${{ env.IMAGE }}:${{ github.sha }}
EOF
# Configure traffic split (10% to canary)
kubectl apply -f - <<EOF
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: app-vs
namespace: ${{ steps.setup.outputs.namespace }}
spec:
hosts:
- app
http:
- match:
- headers:
x-canary:
exact: "true"
route:
- destination:
host: app
subset: canary
weight: 100
- route:
- destination:
host: app
subset: stable
weight: 90
- destination:
host: app
subset: canary
weight: 10
EOF
# Monitor metrics
./scripts/monitor-canary.sh
# Promote or rollback based on metrics
if ./scripts/check-canary-metrics.sh; then
echo "Promoting canary to stable"
kubectl set image deployment/app \
app=${{ env.IMAGE }}:${{ github.sha }} \
-n ${{ steps.setup.outputs.namespace }}
kubectl delete deployment app-canary \
-n ${{ steps.setup.outputs.namespace }}
else
echo "Rolling back canary"
kubectl delete deployment app-canary \
-n ${{ steps.setup.outputs.namespace }}
exit 1
fi
.github/workflows/ai-assisted-ci.yml
# AI Prompt: "Integrate Claude Code into CI/CD pipeline"
name: AI-Assisted CI/CD
on:
issue_comment:
types: [created]
pull_request_review_comment:
types: [created]
jobs:
claude-assistance:
if: contains(github.event.comment.body, '@claude')
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Claude Code
run: |
npm install -g @anthropic-ai/claude-code
claude --version
- name: Configure Claude
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
run: |
claude config set apiKey "$ANTHROPIC_API_KEY"
- name: Process Request
id: claude
run: |
COMMENT="${{ github.event.comment.body }}"
TASK=${COMMENT#"@claude "}
# Run Claude with the task
claude --yolo --max-turns 10 "$TASK" > claude-output.txt
# Extract changes
git diff > changes.diff
- name: Create PR with Changes
if: steps.claude.outputs.has-changes
uses: peter-evans/create-pull-request@v5
with:
token: ${{ secrets.GITHUB_TOKEN }}
commit-message: "AI: ${{ github.event.comment.body }}"
title: "AI Assistant: Implementing requested changes"
body: |
This PR was created by Claude Code in response to:
> ${{ github.event.comment.body }}
## Changes Made
\`\`\`diff
$(cat changes.diff)
\`\`\`
## Claude's Summary
$(cat claude-output.txt)
branch: ai-changes-${{ github.run_id }}

CI/CD Best Practices

  1. Version Everything - Treat CI/CD configs as code
  2. Fail Fast - Run quick checks first
  3. Cache Aggressively - Speed up builds with smart caching
  4. Parallelize - Run independent jobs concurrently
  5. Security First - Scan early and often
  6. Monitor Everything - Track metrics and failures
# AI: "Optimize CI/CD caching"
# GitHub Actions
- uses: actions/cache@v3
with:
path: |
~/.npm
~/.cache
node_modules
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
# GitLab CI
cache:
key:
files:
- package-lock.json
paths:
- .npm/
- node_modules/
policy: pull-push
Terminal window
# AI Prompt: "Generate CI/CD debugging commands"
# GitHub Actions debugging
- name: Debug Context
run: |
echo "Event: ${{ github.event_name }}"
echo "Ref: ${{ github.ref }}"
echo "SHA: ${{ github.sha }}"
echo "Actor: ${{ github.actor }}"
echo "Workflow: ${{ github.workflow }}"
echo "Run ID: ${{ github.run_id }}"
echo "Run Number: ${{ github.run_number }}"
# Enable debug logging
env:
ACTIONS_RUNNER_DEBUG: true
ACTIONS_STEP_DEBUG: true
# GitLab CI debugging
debug_job:
script:
- echo "CI_COMMIT_SHA=$CI_COMMIT_SHA"
- echo "CI_COMMIT_REF_NAME=$CI_COMMIT_REF_NAME"
- echo "CI_PIPELINE_ID=$CI_PIPELINE_ID"
- echo "CI_JOB_ID=$CI_JOB_ID"
- env | grep CI_
# Jenkins debugging
sh 'printenv | sort'
sh 'echo "Workspace: $WORKSPACE"'
sh 'echo "Build URL: $BUILD_URL"'