CI/CD pipeline standards for GitHub Actions, GitLab CI, and deployment automation. Covers testing gates, security scanning, artifact management, and deployment strategies for reliable software delivery.
Quick Navigation: Level 1: Quick Start (5 min) → Level 2: Implementation (30 min) → Level 3: Mastery (Extended)
# .github/workflows/ci.yml
name: CI Pipeline
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run linters
run: npm run lint
- name: Run tests
run: npm test -- --coverage
- name: Check coverage threshold
run: |
COVERAGE=$(cat coverage/coverage-summary.json | jq '.total.lines.pct')
if (( $(echo "$COVERAGE < 80" | bc -l) )); then
echo "Coverage $COVERAGE% is below 80% threshold"
exit 1
fi
security:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run security audit
run: npm audit --audit-level=high
- name: SAST scan
uses: github/codeql-action/analyze@v2
build:
needs: [test, security]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build Docker image
run: docker build -t myapp:${{ github.sha }} .
- name: Scan container
uses: aquasecurity/trivy-action@master
with:
image-ref: 'myapp:${{ github.sha }}'
severity: 'CRITICAL,HIGH'
exit-code: '1'
- name: Push to registry
run: |
echo "${{ secrets.DOCKER_PASSWORD }}" | docker login -u "${{ secrets.DOCKER_USERNAME }}" --password-stdin
docker tag myapp:${{ github.sha }} registry.example.com/myapp:${{ github.sha }}
docker push registry.example.com/myapp:${{ github.sha }}
Multi-Stage Pipeline
# .github/workflows/cd.yml
name: CD Pipeline
on:
push:
branches: [main]
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
deploy-staging:
runs-on: ubuntu-latest
environment:
name: staging
url: https://staging.example.com
steps:
- uses: actions/checkout@v4
- name: Deploy to staging
run: |
kubectl set image deployment/myapp \
myapp=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }} \
--namespace=staging
- name: Wait for rollout
run: kubectl rollout status deployment/myapp -n staging --timeout=5m
- name: Run smoke tests
run: |
curl -f https://staging.example.com/health || exit 1
deploy-production:
needs: deploy-staging
runs-on: ubuntu-latest
environment:
name: production
url: https://example.com
steps:
- uses: actions/checkout@v4
- name: Blue-green deployment
run: |
# Deploy to green environment
kubectl set image deployment/myapp-green \
myapp=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }} \
--namespace=production
# Wait for green to be ready
kubectl rollout status deployment/myapp-green -n production --timeout=5m
# Switch traffic to green
kubectl patch service myapp -n production \
-p '{"spec":{"selector":{"version":"green"}}}'
Caching Strategy
- name: Cache dependencies
uses: actions/cache@v3
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
- name: Cache Docker layers
uses: actions/cache@v3
with:
path: /tmp/.buildx-cache
key: ${{ runner.os }}-buildx-${{ github.sha }}
restore-keys: |
${{ runner.os }}-buildx-
Complete Pipeline
# .gitlab-ci.yml
stages:
- validate
- build
- test
- scan
- deploy
variables:
DOCKER_DRIVER: overlay2
IMAGE_TAG: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
cache:
paths:
- node_modules/
- .npm/
validate:
stage: validate
image: node:18-alpine
script:
- npm ci
- npm run lint
- npm run typecheck
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
- if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
unit-tests:
stage: test
image: node:18-alpine
script:
- npm ci
- npm test -- --coverage
coverage: '/Lines\s*:\s*(\d+\.\d+)%/'
artifacts:
reports:
coverage_report:
coverage_format: cobertura
path: coverage/cobertura-coverage.xml
security-scan:
stage: scan
image: aquasec/trivy:latest
script:
- trivy fs --severity CRITICAL,HIGH --exit-code 1 .
- trivy config --severity CRITICAL,HIGH --exit-code 1 .
build-image:
stage: build
image: docker:24-dind
services:
- docker:24-dind
before_script:
- echo "$CI_REGISTRY_PASSWORD" | docker login -u "$CI_REGISTRY_USER" --password-stdin $CI_REGISTRY
script:
- docker build -t $IMAGE_TAG .
- docker push $IMAGE_TAG
only:
- main
- develop
deploy-staging:
stage: deploy
image: bitnami/kubectl:latest
script:
- kubectl set image deployment/myapp myapp=$IMAGE_TAG --namespace=staging
- kubectl rollout status deployment/myapp -n staging --timeout=5m
environment:
name: staging
url: https://staging.example.com
only:
- develop
deploy-production:
stage: deploy
image: bitnami/kubectl:latest
script:
- kubectl set image deployment/myapp myapp=$IMAGE_TAG --namespace=production
- kubectl rollout status deployment/myapp -n production --timeout=5m
environment:
name: production
url: https://example.com
when: manual
only:
- main
Comprehensive Test Strategy
# GitHub Actions example
jobs:
unit-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npm test -- --coverage --maxWorkers=2
- name: Upload coverage
uses: codecov/codecov-action@v3
with:
files: ./coverage/lcov.info
fail_ci_if_error: true
integration-tests:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:15
env:
POSTGRES_PASSWORD: postgres
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npm run test:integration
env:
DATABASE_URL: postgresql://postgres:postgres@localhost:5432/test
e2e-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npx playwright install --with-deps
- run: npm run test:e2e
- name: Upload test results
if: always()
uses: actions/upload-artifact@v3
with:
name: playwright-report
path: playwright-report/
Multi-Layer Security
security:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# Dependency scanning
- name: Run npm audit
run: npm audit --audit-level=moderate
# SAST (Static Application Security Testing)
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
languages: javascript
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
# Container scanning
- name: Build image
run: docker build -t myapp:test .
- name: Run Trivy scan
uses: aquasecurity/trivy-action@master
with:
image-ref: 'myapp:test'
format: 'sarif'
output: 'trivy-results.sarif'
severity: 'CRITICAL,HIGH'
- name: Upload Trivy results
uses: github/codeql-action/upload-sarif@v2
with:
sarif_file: 'trivy-results.sarif'
# Secrets scanning
- name: Gitleaks scan
uses: gitleaks/gitleaks-action@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Docker Image Tagging Strategy
# Tag with semantic version
docker tag myapp:latest registry.example.com/myapp:v1.2.3
# Tag with git SHA (for traceability)
docker tag myapp:latest registry.example.com/myapp:${GIT_SHA:0:7}
# Tag with environment
docker tag myapp:latest registry.example.com/myapp:production
# Tag with date (for retention policies)
docker tag myapp:latest registry.example.com/myapp:$(date +%Y%m%d)
Artifact Retention Policy (see templates/.github/workflows/cleanup.yml):
name: Cleanup Old Artifacts
on:
schedule:
- cron: '0 2 * * 0' # Weekly on Sunday at 2 AM
jobs:
cleanup:
runs-on: ubuntu-latest
steps:
- name: Delete old artifacts
uses: actions/github-script@v6
with:
script: |
const artifacts = await github.rest.actions.listArtifactsForRepo({
owner: context.repo.owner,
repo: context.repo.repo,
});
const cutoffDate = new Date();
cutoffDate.setDate(cutoffDate.getDate() - 30); // 30 days retention
for (const artifact of artifacts.data.artifacts) {
if (new Date(artifact.created_at) < cutoffDate) {
await github.rest.actions.deleteArtifact({
owner: context.repo.owner,
repo: context.repo.repo,
artifact_id: artifact.id,
});
}
}
Blue-Green Deployment (see scripts/deploy.sh):
#!/bin/bash
set -e
ENVIRONMENT=${1:-staging}
VERSION=${2:-latest}
NAMESPACE="myapp-${ENVIRONMENT}"
echo "Deploying version $VERSION to $ENVIRONMENT"
# Deploy to inactive environment (green)
kubectl set image deployment/myapp-green \
myapp=registry.example.com/myapp:$VERSION \
--namespace=$NAMESPACE
# Wait for green to be ready
kubectl rollout status deployment/myapp-green -n $NAMESPACE --timeout=5m
# Run smoke tests against green
echo "Running smoke tests..."
GREEN_URL=$(kubectl get service myapp-green -n $NAMESPACE -o jsonpath='{.status.loadBalancer.ingress[0].hostname}')
curl -f "http://${GREEN_URL}/health" || exit 1
# Switch traffic to green
echo "Switching traffic to green..."
kubectl patch service myapp -n $NAMESPACE \
-p '{"spec":{"selector":{"version":"green"}}}'
echo "Deployment successful! Old blue environment remains for rollback."
Canary Deployment
# Kubernetes Deployment with Istio
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: myapp
spec:
hosts:
- myapp.example.com
http:
- match:
- headers:
x-canary:
exact: "true"
route:
- destination:
host: myapp
subset: canary
- route:
- destination:
host: myapp
subset: stable
weight: 90
- destination:
host: myapp
subset: canary
weight: 10 # 10% traffic to canary
Rolling Update (default Kubernetes strategy):
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
spec:
replicas: 10
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 2 # Max 2 new pods above desired count
maxUnavailable: 1 # Max 1 pod can be unavailable
template:
spec:
containers:
- name: myapp
image: registry.example.com/myapp:v1.2.3
readinessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
Environment Configuration
# GitHub Environments with protection rules
environments:
staging:
url: https://staging.example.com
protection_rules:
- type: required_reviewers
reviewers: []
- type: wait_timer
wait_timer: 0
production:
url: https://example.com
protection_rules:
- type: required_reviewers
reviewers:
- devops-team
- type: wait_timer
wait_timer: 300 # 5 minute wait
Secrets Management
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- 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: Fetch secrets from Parameter Store
run: |
export DB_PASSWORD=$(aws ssm get-parameter \
--name /myapp/production/db-password \
--with-decryption \
--query 'Parameter.Value' \
--output text)
- name: Deploy with secrets
run: |
kubectl create secret generic myapp-secrets \
--from-literal=db-password=$DB_PASSWORD \
--namespace=production \
--dry-run=client -o yaml | kubectl apply -f -
Automated Rollback on Failure
deploy:
runs-on: ubuntu-latest
steps:
- name: Deploy new version
id: deploy
run: |
kubectl set image deployment/myapp myapp=$IMAGE_TAG --namespace=production
kubectl rollout status deployment/myapp -n production --timeout=5m
- name: Health check
id: health_check
run: |
sleep 30 # Wait for metrics
ERROR_RATE=$(curl -s https://example.com/metrics | jq '.error_rate')
if (( $(echo "$ERROR_RATE > 0.05" | bc -l) )); then
echo "Error rate ${ERROR_RATE} exceeds 5% threshold"
exit 1
fi
- name: Rollback on failure
if: failure()
run: |
echo "Deployment failed, rolling back..."
kubectl rollout undo deployment/myapp -n production
kubectl rollout status deployment/myapp -n production --timeout=5m
- name: Notify on rollback
if: failure()
uses: slackapi/slack-github-action@v1
with:
payload: |
{
"text": "🚨 Production deployment rolled back!",
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*Rollback Alert*\nDeployment to production failed and was automatically rolled back.\n\n*Run:* ${{ github.run_id }}\n*Commit:* ${{ github.sha }}"
}
}
]
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }}
Metrics and Alerting
- name: Track deployment metrics
run: |
curl -X POST https://metrics.example.com/api/v1/events \
-H "Content-Type: application/json" \
-d '{
"event": "deployment",
"environment": "production",
"version": "${{ github.sha }}",
"status": "success",
"duration": "${{ steps.deploy.outputs.duration }}",
"timestamp": "'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"
}'
- name: Update status page
if: success()
run: |
curl -X POST https://status.example.com/api/v2/incidents \
-H "Authorization: Bearer ${{ secrets.STATUS_PAGE_TOKEN }}" \
-d '{
"incident": {
"name": "Deployment Complete",
"status": "resolved",
"body": "Production deployment completed successfully."
}
}'
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
node-version: [16, 18, 20]
exclude:
- os: macos-latest
node-version: 16
# .github/actions/setup-env/action.yml
name: 'Setup Environment'
description: 'Setup Node.js and dependencies'
runs:
using: "composite"
steps:
- uses: actions/setup-node@v4
with:
node-version: '18'
cache: 'npm'
- run: npm ci
shell: bash
# .github/workflows/deploy-reusable.yml
on:
workflow_call:
inputs:
environment:
required: true
type: string
secrets:
deploy-token:
required: true
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- run: ./deploy.sh ${{ inputs.environment }}
if conditions// TODO: Add basic example for ci-cd
// This example demonstrates core functionality
// TODO: Add advanced example for ci-cd
// This example shows production-ready patterns
// TODO: Add integration example showing how ci-cd
// works with other systems and services
See examples/ci-cd/ for complete working examples.
This skill integrates with:
Problem: Not testing edge cases and error conditions leads to production bugs
Solution: Implement comprehensive test coverage including:
Prevention: Enforce minimum code coverage (80%+) in CI/CD pipeline
Problem: Hardcoding values makes applications inflexible and environment-dependent
Solution: Use environment variables and configuration management:
Prevention: Use tools like dotenv, config validators, and secret scanners
Problem: Security vulnerabilities from not following established security patterns
Solution: Follow security guidelines:
Prevention: Use security linters, SAST tools, and regular dependency updates
Best Practices:
Last Updated: 2025-01-17 Version: 1.0.0 Quality Score: 95/100
Search for places (restaurants, cafes, etc.) via Google Places API proxy on localhost.
Interact with GitHub using the `gh` CLI. Use `gh issue`, `gh pr`, `gh run`, and `gh api` for issues, PRs, CI runs, and advanced queries.
Create or update AgentSkills. Use when designing, structuring, or packaging skills with scripts, references, and assets.
Start voice calls via the OpenClaw voice-call plugin.
Notion API for creating and managing pages, databases, and blocks.
Gemini CLI for one-shot Q&A, summaries, and generation.
Category:developer