
Devops Workflow Engineer
- 125 installs
- 451 repo stars
- Updated July 21, 2026
- borghei/claude-skills
Design CI/CD pipelines, deployment workflows, environment promotion, and infrastructure automation connecting build, test, and release steps across cloud targets.
About
Designs DevOps delivery workflows including CI/CD pipelines, environment promotion, GitHub Actions configuration, container builds, deployment automation, and release gates for reliable software shipping.
- CI/CD pipeline architecture
- Environment promotion strategies
- GitHub Actions and workflow YAML
- Container build and deploy automation
- Release gates and rollback hooks
Devops Workflow Engineer by the numbers
- 125 all-time installs (skills.sh)
- Ranked #506 of 1,435 DevOps & CI/CD skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/borghei/claude-skills --skill devops-workflow-engineerAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 125 |
|---|---|
| repo stars | ★ 451 |
| Last updated | July 21, 2026 |
| Repository | borghei/claude-skills ↗ |
What it does
Design CI/CD pipelines, deployment workflows, environment promotion, and infrastructure automation connecting build, test, and release steps across cloud targets.
Files
DevOps Workflow Engineer
Generate GitHub Actions workflow YAML, analyze existing pipelines for optimization opportunities, and create deployment plans with strategy selection, health checks, and rollback procedures.
Core Capabilities
- CI pipeline design — fail-fast job ordering (lint → unit → build → integration → security) with matrix testing and CI time/flake/cache targets.
- CD & multi-environment — dev/staging/prod promotion flows, build-once-deploy-everywhere, environment protection rules, and rollback at every stage.
- Pipeline optimization — detect missing caching, missing timeouts, serial chains, deprecated actions, leaked secrets, and oversized runners; apply path filtering and concurrency cancellation.
- Deployment strategies — choose blue-green, canary, or rolling via decision tree; canary traffic-split schedule with promotion gates.
- GitHub Actions patterns — reusable workflows, OIDC auth, secrets hierarchy, and runner cost estimation.
When to Use
- Designing a new CI or CD workflow from scratch.
- Planning a multi-environment (dev/staging/prod) deployment.
- Optimizing an existing pipeline's cost or runtime.
- Implementing a blue-green, canary, or rolling deployment strategy.
Clarify First
Before generating the workflow, confirm these inputs. If any is unknown or vague, ASK — do not assume:
- [ ] Workflow type — CI, CD, release, or security-scan (sets
workflow_generator.py --type) - [ ] Stack — language and test framework (e.g. python/pytest) (drives the generated YAML steps via
--language/--test-framework) - [ ] Deployment strategy & environments — blue-green, canary, or rolling, and which of dev/staging/prod (drives the
deployment_planner.pyplan)
Stop rule: ask only the 2-3 that most change the output. If the user says "just draft it," proceed and list your assumptions at the top of the artifact.
Tools
| Tool | Purpose | Command |
|---|---|---|
workflow_generator.py | Generate GitHub Actions YAML (ci, cd, release, security-scan, docs-check) | python scripts/workflow_generator.py --type ci --language python --test-framework pytest |
pipeline_analyzer.py | Analyze workflows for optimization findings, cost estimates, severity ratings | python scripts/pipeline_analyzer.py .github/workflows/ --format json |
deployment_planner.py | Generate a deployment plan with strategy, health checks, rollback | python scripts/deployment_planner.py --type webapp --environments dev,staging,prod --strategy canary |
All tools support --format json and --output/-o for file writing.
References
Load the reference that matches the task — keep this file lean and pull detail on demand:
- [references/workflows-and-optimization.md](references/workflows-and-optimization.md) — the CI / CD / optimization workflows with full YAML, deployment-strategy decision tree and canary schedule, GitHub Actions patterns, runner cost table, anti-patterns, and troubleshooting. Read when building or tuning a pipeline.
- [references/github-actions-patterns.md](references/github-actions-patterns.md) — deep GitHub Actions pattern library. Read when authoring advanced workflow YAML.
- [references/deployment-strategies.md](references/deployment-strategies.md) — deep deployment strategy guide (blue-green, canary, rolling). Read when planning a release rollout.
- [references/agentic-workflows-guide.md](references/agentic-workflows-guide.md) — agentic/automated workflow patterns. Read when wiring up AI-driven or autonomous pipeline steps.
Integration Points
| Skill | Integration |
|---|---|
release-orchestrator | Release workflows align with versioning and changelog |
senior-devops | Deployment strategies complement infra automation |
senior-secops | Security scanning steps feed SecOps dashboards |
senior-qa | CI quality gates map to QA acceptance criteria |
incident-commander | Rollback procedures connect to incident playbooks |
# =============================================================================
# CD Workflow Template -- Multi-Environment Deploy
# =============================================================================
#
# Production-ready continuous delivery workflow with multi-environment
# deployment, Docker image building, health checks, and rollback support.
#
# Usage:
# 1. Copy this file to .github/workflows/cd.yml
# 2. Customize the env block with your registry and app name
# 3. Configure GitHub Environments (dev, staging, production) with
# appropriate protection rules and secrets
# 4. Set required secrets per environment:
# - DEPLOY_TOKEN or KUBECONFIG (for deployment)
# - HEALTH_CHECK_URL (for post-deploy verification)
#
# Deployment flow:
# Build -> Dev (auto) -> Staging (auto) -> Production (manual approval)
#
# =============================================================================
name: CD
on:
push:
branches: [main]
paths:
- 'src/**'
- 'Dockerfile'
- '.github/workflows/cd.yml'
workflow_dispatch:
inputs:
environment:
description: 'Target environment'
required: true
type: choice
options:
- dev
- staging
- production
default: staging
skip_tests:
description: 'Skip pre-deploy tests'
required: false
type: boolean
default: false
permissions:
contents: read
packages: write
id-token: write
concurrency:
group: cd-${{ github.ref }}
cancel-in-progress: false # Never cancel in-progress deployments
# ---------------------------------------------------------------------------
# Environment variables -- customize for your project
# ---------------------------------------------------------------------------
env:
REGISTRY: ghcr.io/${{ github.repository_owner }}
APP_NAME: ${{ github.event.repository.name }}
# Timeout for health checks (seconds)
HEALTH_CHECK_TIMEOUT: 30
# Number of health check retries
HEALTH_CHECK_RETRIES: 5
# ===========================================================================
# Jobs
# ===========================================================================
jobs:
# -------------------------------------------------------------------------
# 1. Build and Push Docker Image
# -------------------------------------------------------------------------
build:
name: Build Image
runs-on: ubuntu-latest
timeout-minutes: 20
outputs:
image_tag: ${{ github.sha }}
image_url: ${{ steps.meta.outputs.tags }}
steps:
- uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.APP_NAME }}
tags: |
type=sha,prefix=
type=ref,event=branch
type=raw,value=latest,enable={{is_default_branch}}
- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
build-args: |
BUILD_SHA=${{ github.sha }}
BUILD_TIME=${{ github.event.head_commit.timestamp }}
- name: Generate build summary
run: |
echo "## Build Summary" >> "$GITHUB_STEP_SUMMARY"
echo "| Detail | Value |" >> "$GITHUB_STEP_SUMMARY"
echo "|--------|-------|" >> "$GITHUB_STEP_SUMMARY"
echo "| Image | \`${{ env.REGISTRY }}/${{ env.APP_NAME }}:${{ github.sha }}\` |" >> "$GITHUB_STEP_SUMMARY"
echo "| Commit | \`${{ github.sha }}\` |" >> "$GITHUB_STEP_SUMMARY"
echo "| Branch | \`${{ github.ref_name }}\` |" >> "$GITHUB_STEP_SUMMARY"
# -------------------------------------------------------------------------
# 2. Pre-Deploy Tests
# -------------------------------------------------------------------------
pre-deploy-tests:
name: Pre-Deploy Tests
needs: build
if: inputs.skip_tests != true
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
- name: Pull built image
run: |
docker pull ${{ env.REGISTRY }}/${{ env.APP_NAME }}:${{ github.sha }}
- name: Run smoke tests against container
run: |
# Start the container
docker run -d \
--name smoke-test \
-p 8080:8080 \
${{ env.REGISTRY }}/${{ env.APP_NAME }}:${{ github.sha }}
# Wait for container to be ready
for i in $(seq 1 ${{ env.HEALTH_CHECK_RETRIES }}); do
if curl -sf http://localhost:8080/health > /dev/null 2>&1; then
echo "Container is healthy"
break
fi
echo "Waiting for container... attempt $i/${{ env.HEALTH_CHECK_RETRIES }}"
sleep 5
done
# Verify health endpoint
curl -sf http://localhost:8080/health || {
echo "::error::Health check failed"
docker logs smoke-test
exit 1
}
# Cleanup
docker stop smoke-test
docker rm smoke-test
# -------------------------------------------------------------------------
# 3. Deploy to Dev (automatic on push to main)
# -------------------------------------------------------------------------
deploy-dev:
name: Deploy to Dev
needs: [build, pre-deploy-tests]
if: |
always() &&
needs.build.result == 'success' &&
(needs.pre-deploy-tests.result == 'success' || needs.pre-deploy-tests.result == 'skipped') &&
(github.event_name == 'push' || inputs.environment == 'dev')
runs-on: ubuntu-latest
timeout-minutes: 15
environment:
name: dev
url: ${{ vars.APP_URL }}
steps:
- uses: actions/checkout@v4
- name: Deploy to dev
run: |
echo "Deploying ${{ env.APP_NAME }}:${{ github.sha }} to dev"
# Replace with your deployment command:
# kubectl set image deployment/${{ env.APP_NAME }} \
# app=${{ env.REGISTRY }}/${{ env.APP_NAME }}:${{ github.sha }} \
# -n dev
# kubectl rollout status deployment/${{ env.APP_NAME }} -n dev --timeout=300s
- name: Verify deployment
run: |
echo "Verifying dev deployment..."
# Replace with actual health check:
# for i in $(seq 1 ${{ env.HEALTH_CHECK_RETRIES }}); do
# if curl -sf "${{ vars.HEALTH_CHECK_URL }}/health" --max-time ${{ env.HEALTH_CHECK_TIMEOUT }}; then
# echo "Dev deployment healthy"
# exit 0
# fi
# sleep 10
# done
# echo "::error::Dev health check failed"
# exit 1
# -------------------------------------------------------------------------
# 4. Deploy to Staging (automatic after dev)
# -------------------------------------------------------------------------
deploy-staging:
name: Deploy to Staging
needs: [build, deploy-dev]
if: |
always() &&
needs.build.result == 'success' &&
(needs.deploy-dev.result == 'success' || inputs.environment == 'staging')
runs-on: ubuntu-latest
timeout-minutes: 15
environment:
name: staging
url: ${{ vars.APP_URL }}
steps:
- uses: actions/checkout@v4
- name: Deploy to staging
run: |
echo "Deploying ${{ env.APP_NAME }}:${{ github.sha }} to staging"
# Replace with your deployment command:
# kubectl set image deployment/${{ env.APP_NAME }} \
# app=${{ env.REGISTRY }}/${{ env.APP_NAME }}:${{ github.sha }} \
# -n staging
# kubectl rollout status deployment/${{ env.APP_NAME }} -n staging --timeout=300s
- name: Verify deployment
run: |
echo "Verifying staging deployment..."
# Replace with actual health check
- name: Run integration tests
run: |
echo "Running integration tests against staging..."
# Replace with actual integration tests:
# pytest tests/integration/ --base-url "${{ vars.APP_URL }}"
# -------------------------------------------------------------------------
# 5. Deploy to Production (manual approval required)
# -------------------------------------------------------------------------
deploy-production:
name: Deploy to Production
needs: [build, deploy-staging]
if: |
always() &&
needs.build.result == 'success' &&
(needs.deploy-staging.result == 'success' || inputs.environment == 'production')
runs-on: ubuntu-latest
timeout-minutes: 20
environment:
name: production
url: ${{ vars.APP_URL }}
steps:
- uses: actions/checkout@v4
- name: Pre-deploy snapshot
run: |
echo "Recording pre-deploy state for rollback..."
# Record current deployment version for rollback:
# kubectl get deployment/${{ env.APP_NAME }} -n production \
# -o jsonpath='{.spec.template.spec.containers[0].image}' > /tmp/previous-image.txt
# echo "Previous image: $(cat /tmp/previous-image.txt)"
- name: Deploy to production
run: |
echo "Deploying ${{ env.APP_NAME }}:${{ github.sha }} to production"
# Replace with your deployment command:
# kubectl set image deployment/${{ env.APP_NAME }} \
# app=${{ env.REGISTRY }}/${{ env.APP_NAME }}:${{ github.sha }} \
# -n production
# kubectl rollout status deployment/${{ env.APP_NAME }} -n production --timeout=300s
- name: Verify deployment
run: |
echo "Verifying production deployment..."
# Replace with actual health check:
# for i in $(seq 1 ${{ env.HEALTH_CHECK_RETRIES }}); do
# if curl -sf "${{ vars.HEALTH_CHECK_URL }}/health" --max-time ${{ env.HEALTH_CHECK_TIMEOUT }}; then
# echo "Production deployment healthy"
# exit 0
# fi
# echo "Health check attempt $i failed, retrying..."
# sleep 10
# done
# echo "::error::Production health check failed -- initiating rollback"
# kubectl rollout undo deployment/${{ env.APP_NAME }} -n production
# exit 1
- name: Post-deploy monitoring window
run: |
echo "Monitoring production for 2 minutes..."
# Replace with actual monitoring check:
# sleep 120
# ERROR_RATE=$(curl -s "${{ vars.METRICS_URL }}/error-rate?window=2m")
# if (( $(echo "$ERROR_RATE > 0.01" | bc -l) )); then
# echo "::error::Error rate $ERROR_RATE exceeds threshold (1%)"
# kubectl rollout undo deployment/${{ env.APP_NAME }} -n production
# exit 1
# fi
# echo "Error rate $ERROR_RATE is within acceptable bounds"
- name: Create deployment annotation
if: success()
run: |
echo "## Production Deployment" >> "$GITHUB_STEP_SUMMARY"
echo "| Detail | Value |" >> "$GITHUB_STEP_SUMMARY"
echo "|--------|-------|" >> "$GITHUB_STEP_SUMMARY"
echo "| Version | \`${{ github.sha }}\` |" >> "$GITHUB_STEP_SUMMARY"
echo "| Environment | production |" >> "$GITHUB_STEP_SUMMARY"
echo "| Status | Deployed |" >> "$GITHUB_STEP_SUMMARY"
echo "| URL | ${{ vars.APP_URL }} |" >> "$GITHUB_STEP_SUMMARY"
# -------------------------------------------------------------------------
# 6. Deployment Summary
# -------------------------------------------------------------------------
summary:
name: Deployment Summary
if: always()
needs: [build, deploy-dev, deploy-staging, deploy-production]
runs-on: ubuntu-latest
timeout-minutes: 2
steps:
- name: Generate deployment report
run: |
echo "## Deployment Report" >> "$GITHUB_STEP_SUMMARY"
echo "" >> "$GITHUB_STEP_SUMMARY"
echo "| Stage | Status |" >> "$GITHUB_STEP_SUMMARY"
echo "|-------|--------|" >> "$GITHUB_STEP_SUMMARY"
echo "| Build | ${{ needs.build.result }} |" >> "$GITHUB_STEP_SUMMARY"
echo "| Dev | ${{ needs.deploy-dev.result }} |" >> "$GITHUB_STEP_SUMMARY"
echo "| Staging | ${{ needs.deploy-staging.result }} |" >> "$GITHUB_STEP_SUMMARY"
echo "| Production | ${{ needs.deploy-production.result }} |" >> "$GITHUB_STEP_SUMMARY"
echo "" >> "$GITHUB_STEP_SUMMARY"
echo "**Commit:** \`${{ github.sha }}\`" >> "$GITHUB_STEP_SUMMARY"
echo "**Triggered by:** ${{ github.actor }}" >> "$GITHUB_STEP_SUMMARY"
# =============================================================================
# CI Workflow Template -- Python + Node.js
# =============================================================================
#
# Production-ready continuous integration workflow for projects that use both
# Python (backend/scripts) and Node.js (frontend/tooling). Includes linting,
# testing, building, and security scanning with caching and matrix strategies.
#
# Usage:
# 1. Copy this file to .github/workflows/ci.yml
# 2. Customize the variables in the env block
# 3. Adjust matrix versions to match your project
# 4. Remove sections you do not need (e.g., Node.js if Python-only)
#
# =============================================================================
name: CI
on:
push:
branches: [main, dev]
paths:
- 'src/**'
- 'tests/**'
- 'packages/**'
- 'requirements*.txt'
- 'package*.json'
- 'Dockerfile'
- '.github/workflows/ci.yml'
paths-ignore:
- '**.md'
- 'docs/**'
- 'LICENSE'
pull_request:
branches: [main]
permissions:
contents: read
concurrency:
group: ci-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
# ---------------------------------------------------------------------------
# Environment variables -- customize for your project
# ---------------------------------------------------------------------------
env:
PYTHON_DEFAULT_VERSION: '3.12'
NODE_DEFAULT_VERSION: '20'
# Set to 'true' to enable the Node.js jobs (remove or set 'false' for Python-only)
ENABLE_NODE: 'true'
# ===========================================================================
# Jobs
# ===========================================================================
jobs:
# -------------------------------------------------------------------------
# 1. Python Lint
# -------------------------------------------------------------------------
python-lint:
name: Python Lint
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: ${{ env.PYTHON_DEFAULT_VERSION }}
cache: pip
- name: Install lint tools
run: pip install ruff
- name: Check formatting
run: ruff format --check .
- name: Check linting rules
run: ruff check .
# -------------------------------------------------------------------------
# 2. Python Tests (matrix)
# -------------------------------------------------------------------------
python-test:
name: Python Test (${{ matrix.python-version }})
needs: python-lint
runs-on: ubuntu-latest
timeout-minutes: 15
strategy:
fail-fast: false
matrix:
python-version: ['3.10', '3.11', '3.12']
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
cache: pip
- name: Install dependencies
run: |
pip install --upgrade pip
pip install -r requirements.txt
pip install -r requirements-dev.txt 2>/dev/null || true
- name: Run tests
run: |
pytest \
--cov=src \
--cov-report=xml:coverage.xml \
--junitxml=results.xml \
-v
- name: Upload test results
uses: actions/upload-artifact@v4
if: always()
with:
name: python-test-results-${{ matrix.python-version }}
path: |
results.xml
coverage.xml
retention-days: 7
# -------------------------------------------------------------------------
# 3. Node.js Lint (conditional)
# -------------------------------------------------------------------------
node-lint:
name: Node.js Lint
if: ${{ vars.ENABLE_NODE != 'false' }}
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_DEFAULT_VERSION }}
cache: npm
- name: Install dependencies
run: npm ci
- name: Lint
run: npm run lint
- name: Type check
run: npm run typecheck 2>/dev/null || echo "No typecheck script defined"
# -------------------------------------------------------------------------
# 4. Node.js Tests (conditional, matrix)
# -------------------------------------------------------------------------
node-test:
name: Node.js Test (Node ${{ matrix.node-version }})
needs: node-lint
if: ${{ vars.ENABLE_NODE != 'false' }}
runs-on: ubuntu-latest
timeout-minutes: 15
strategy:
fail-fast: false
matrix:
node-version: ['18', '20', '22']
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: npm
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm test -- --coverage --ci
- name: Upload test results
uses: actions/upload-artifact@v4
if: always()
with:
name: node-test-results-${{ matrix.node-version }}
path: |
coverage/
junit.xml
retention-days: 7
# -------------------------------------------------------------------------
# 5. Build Verification
# -------------------------------------------------------------------------
build:
name: Build
needs: [python-test, node-test]
if: always() && !cancelled()
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: ${{ env.PYTHON_DEFAULT_VERSION }}
cache: pip
- uses: actions/setup-node@v4
if: ${{ vars.ENABLE_NODE != 'false' }}
with:
node-version: ${{ env.NODE_DEFAULT_VERSION }}
cache: npm
- name: Install Python dependencies
run: pip install -r requirements.txt
- name: Install Node dependencies
if: ${{ vars.ENABLE_NODE != 'false' }}
run: npm ci
- name: Build Python package
run: |
pip install build
python -m build 2>/dev/null || echo "No Python build configured"
- name: Build Node package
if: ${{ vars.ENABLE_NODE != 'false' }}
run: npm run build 2>/dev/null || echo "No Node build configured"
- name: Build Docker image (verify only)
if: hashFiles('Dockerfile') != ''
run: |
docker build \
--tag ci-build-test:${{ github.sha }} \
--file Dockerfile \
.
# -------------------------------------------------------------------------
# 6. Security Scanning
# -------------------------------------------------------------------------
security:
name: Security Scan
needs: python-lint
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: ${{ env.PYTHON_DEFAULT_VERSION }}
cache: pip
- name: Python dependency audit
run: |
pip install pip-audit
pip-audit -r requirements.txt || true
- name: Node dependency audit
if: hashFiles('package-lock.json') != ''
run: npm audit --audit-level=high || true
- name: Scan for secrets
uses: trufflesecurity/trufflehog@main
with:
extra_args: --only-verified
# -------------------------------------------------------------------------
# 7. CI Gate (required status check)
# -------------------------------------------------------------------------
ci-gate:
name: CI Gate
if: always()
needs: [python-lint, python-test, build, security]
runs-on: ubuntu-latest
timeout-minutes: 2
steps:
- name: Check results
run: |
echo "Python Lint: ${{ needs.python-lint.result }}"
echo "Python Test: ${{ needs.python-test.result }}"
echo "Build: ${{ needs.build.result }}"
echo "Security: ${{ needs.security.result }}"
if [[ "${{ needs.python-lint.result }}" == "failure" ]] || \
[[ "${{ needs.python-test.result }}" == "failure" ]]; then
echo "::error::Required checks failed"
exit 1
fi
echo "All required checks passed."
- name: Generate summary
if: always()
run: |
echo "## CI Results" >> "$GITHUB_STEP_SUMMARY"
echo "| Check | Status |" >> "$GITHUB_STEP_SUMMARY"
echo "|-------|--------|" >> "$GITHUB_STEP_SUMMARY"
echo "| Python Lint | ${{ needs.python-lint.result }} |" >> "$GITHUB_STEP_SUMMARY"
echo "| Python Test | ${{ needs.python-test.result }} |" >> "$GITHUB_STEP_SUMMARY"
echo "| Build | ${{ needs.build.result }} |" >> "$GITHUB_STEP_SUMMARY"
echo "| Security | ${{ needs.security.result }} |" >> "$GITHUB_STEP_SUMMARY"
GitHub Agentic Workflows Guide
Reference for GitHub's agentic workflow system (2026): markdown-based AI automation definitions, safe-outputs, tool permissions, continuous automation categories, and setup with the gh-aw CLI.
---
Table of Contents
- Overview
- What Are Agentic Workflows?
- Markdown-Based Workflow Format
- Frontmatter Schema
- Safe-Outputs Concept
- Tool Permissions
- Six Continuous Automation Categories
- Setup with gh-aw CLI
- Examples
- Integration with Traditional GitHub Actions
- Best Practices
- Limitations and Considerations
---
Overview
GitHub agentic workflows are a new automation layer that enables AI-driven tasks within GitHub repositories. Unlike traditional GitHub Actions (which use YAML-based declarative definitions), agentic workflows are defined in markdown files with YAML frontmatter, written in natural language, and executed by an AI agent with access to repository tools.
Agentic workflows complement, rather than replace, GitHub Actions. They are best suited for tasks that benefit from AI reasoning: code review, documentation generation, triage, and security analysis.
---
What Are Agentic Workflows?
Agentic workflows are:
- Markdown files stored in
.github/agents/within a repository. - Triggered by GitHub events (pull_request, push, issues, schedule, etc.).
- Executed by an AI agent that reads the markdown instructions and uses declared tools.
- Permission-scoped to limit what the agent can access.
- Safe by default -- outputs are labeled as AI-generated and require human review.
Key Differences from GitHub Actions
| Aspect | GitHub Actions | Agentic Workflows |
|---|---|---|
| Definition format | YAML | Markdown + YAML frontmatter |
| Execution model | Declarative steps | AI-driven reasoning |
| Logic | Explicit conditionals | Natural language instructions |
| Tool access | Shell commands, actions | Declared tool permissions |
| Output control | Direct write | Safe-outputs (labeled, auditable) |
| Best for | Deterministic automation | Reasoning-heavy tasks |
---
Markdown-Based Workflow Format
Agentic workflows are stored as markdown files in .github/agents/:
.github/
agents/
code-review.md
triage-issues.md
update-docs.md
security-scan.mdStructure
Each file has two parts:
1. YAML frontmatter -- Metadata, triggers, tools, and permissions. 2. Markdown body -- Natural language instructions for the AI agent.
Minimal Example
---
name: code-review-agent
description: Reviews pull requests for quality, security, and conventions
triggers:
- pull_request
tools:
- code-search
- file-read
- comment-create
permissions:
contents: read
pull-requests: write
safe-outputs: true
---
# Code Review Agent
Review every pull request for:
1. Security vulnerabilities and credential leaks
2. Performance regressions
3. Test coverage gaps
4. Adherence to project coding conventions
## Process
- Read the pull request diff and understand the context
- Search the codebase for related files that might be affected
- Post inline comments for specific issues found
- Post a summary comment with an overall assessment---
Frontmatter Schema
The YAML frontmatter defines the workflow's identity, triggers, capabilities, and constraints.
Required Fields
| Field | Type | Description |
|---|---|---|
name | string | Unique identifier for the workflow (kebab-case) |
description | string | What the workflow does (for UI display) |
triggers | list | GitHub events that activate this workflow |
Optional Fields
| Field | Type | Default | Description |
|---|---|---|---|
tools | list | [] | Tools the agent can access |
permissions | map | {} | GitHub token permissions |
safe-outputs | boolean | true | Label outputs as AI-generated |
model | string | (platform default) | AI model to use |
max-iterations | integer | 10 | Max reasoning loops |
timeout-minutes | integer | 15 | Execution timeout |
concurrency | map | {} | Concurrency group settings |
Triggers
Agentic workflows support the same event triggers as GitHub Actions:
triggers:
- pull_request # Fires on PR open, sync, reopen
- push # Fires on push to branches
- issues # Fires on issue open, edit, label
- issue_comment # Fires on new comments
- schedule # Cron-based scheduling
- workflow_dispatch # Manual trigger
- release # Fires on release eventsTrigger with Filters
triggers:
- type: pull_request
branches: [main, release/*]
paths: ['src/**', 'tests/**']
- type: schedule
cron: '0 9 * * 1' # Mondays at 9 AM UTC---
Safe-Outputs Concept
The safe-outputs: true flag (enabled by default) ensures that everything the agent produces is:
1. Labeled as AI-generated -- Comments, commits, and issues include an AI attribution badge. 2. Not auto-merged -- PR changes created by the agent require human approval. 3. Not auto-deployed -- Deployment triggers are suppressed for agent-created artifacts. 4. Fully auditable -- Every action is logged with the agent's reasoning chain.
Why Safe-Outputs Matters
- Prevents AI hallucinations from reaching production unreviewed.
- Maintains human accountability for all deployed changes.
- Provides an audit trail for compliance.
- Builds trust incrementally as teams gain confidence in AI outputs.
Disabling Safe-Outputs
For low-risk automation (like labeling or triage), you can disable safe-outputs:
safe-outputs: false # Outputs are applied directly without AI labelingUse with caution. Only disable for:
- Read-only operations (search, analysis)
- Low-risk write operations (adding labels, assigning reviewers)
- Internal tooling where all outputs are reviewed by other processes
---
Tool Permissions
Agentic workflows declare which tools the agent can use. Each tool maps to a specific capability and required permission.
Available Tools
| Tool | Capability | Required Permission |
|---|---|---|
code-search | Search repository code and file names | contents: read |
file-read | Read file contents | contents: read |
file-write | Create or modify files | contents: write |
comment-create | Post comments on PRs and issues | pull-requests: write or issues: write |
issue-create | Create new issues | issues: write |
issue-update | Update issue labels, assignees, state | issues: write |
pr-review | Submit PR reviews (approve, request changes) | pull-requests: write |
workflow-trigger | Trigger other workflows | actions: write |
web-search | Search the web for documentation | (no permission needed) |
run-command | Execute shell commands in sandbox | contents: write |
Permission Scoping
Follow the principle of least privilege:
# Good: minimal permissions
permissions:
contents: read
pull-requests: write
# Avoid: overly broad
permissions:
contents: write # Only needed if the agent creates/modifies files
actions: write # Only needed if the agent triggers workflowsTool + Permission Matrix
# Read-only agent (code review)
tools: [code-search, file-read, comment-create]
permissions:
contents: read
pull-requests: write
# Write agent (auto-fix)
tools: [code-search, file-read, file-write, comment-create]
permissions:
contents: write
pull-requests: write
# Triage agent (issue management)
tools: [issue-update, comment-create]
permissions:
issues: write---
Six Continuous Automation Categories
GitHub organizes agentic workflows into six categories of continuous automation:
1. Code Quality
Automated code review, style enforcement, and quality checks.
Triggers: pull_request Tools: code-search, file-read, comment-create, pr-review Examples:
- Review PRs for security vulnerabilities
- Check adherence to coding conventions
- Identify performance anti-patterns
- Suggest refactoring opportunities
2. Documentation
Automated documentation generation, updates, and verification.
Triggers: push (to main), pull_request Tools: code-search, file-read, file-write, comment-create Examples:
- Generate changelog entries from PR titles
- Update API documentation when endpoints change
- Verify README accuracy
- Generate code documentation
3. Security
Automated security scanning, vulnerability detection, and remediation.
Triggers: push, schedule, pull_request Tools: code-search, file-read, comment-create, issue-create Examples:
- Scan for hardcoded secrets
- Detect dependency vulnerabilities
- Review infrastructure-as-code for misconfigurations
- Generate security advisories
4. Release Management
Automated versioning, release notes, and publishing.
Triggers: release, workflow_dispatch, push (tags) Tools: code-search, file-read, file-write, comment-create Examples:
- Draft release notes from merged PRs
- Bump version numbers
- Validate release checklists
- Generate migration guides
5. Issue Triage
Automated issue labeling, assignment, and prioritization.
Triggers: issues, issue_comment Tools: issue-update, comment-create, code-search Examples:
- Label issues by type (bug, feature, question)
- Assign issues to relevant team members
- Detect duplicate issues
- Prioritize based on severity
6. Maintenance
Automated dependency updates, cleanup, and housekeeping.
Triggers: schedule Tools: code-search, file-read, file-write, issue-create Examples:
- Check for outdated dependencies
- Clean up stale branches
- Archive old issues
- Verify CI configuration health
---
Setup with gh-aw CLI
The gh-aw CLI extension manages agentic workflows from the command line.
Installation
# Install the gh-aw extension
gh extension install github/gh-awCommands
# List all agentic workflows in the repository
gh aw list
# Create a new agentic workflow from a template
gh aw create --name code-review --category code-quality
# Validate workflow files
gh aw validate
# Run a workflow manually (for testing)
gh aw run code-review --event pull_request --pr 42
# View workflow execution logs
gh aw logs code-review --run 12345
# Disable a workflow
gh aw disable code-review
# Enable a workflow
gh aw enable code-reviewCreating from Templates
# List available templates
gh aw templates
# Create from template
gh aw create --template security-scanner
gh aw create --template pr-reviewer
gh aw create --template issue-triager
gh aw create --template doc-generator
gh aw create --template release-drafter
gh aw create --template dependency-checkerValidation
# Validate all workflow files
gh aw validate
# Output:
# .github/agents/code-review.md [VALID]
# .github/agents/triage-issues.md [VALID]
# .github/agents/update-docs.md [WARNING] Missing description
# .github/agents/security-scan.md [ERROR] Invalid tool: 'deploy-trigger'---
Examples
Example 1: PR Code Reviewer
---
name: pr-code-reviewer
description: Reviews pull requests for quality, security, and best practices
triggers:
- pull_request
tools:
- code-search
- file-read
- comment-create
- pr-review
permissions:
contents: read
pull-requests: write
safe-outputs: true
timeout-minutes: 10
---
# PR Code Reviewer
## Instructions
Review the pull request thoroughly:
1. Read the complete diff to understand what changed
2. Search for related files that might be affected by the changes
3. Check for security issues: SQL injection, XSS, credential exposure
4. Check for performance issues: N+1 queries, unbounded loops, large allocations
5. Verify test coverage: new code should have corresponding tests
6. Check code style: consistent with project conventions
## Output
Post inline comments for specific issues with severity labels:
- [CRITICAL] Security vulnerabilities or data loss risks
- [WARNING] Performance issues or potential bugs
- [SUGGESTION] Style improvements or refactoring opportunities
Post a summary comment with:
- Overall assessment (approve, request changes, or comment)
- Risk level (low/medium/high)
- List of findings grouped by severityExample 2: Issue Triager
---
name: issue-triager
description: Automatically labels and assigns new issues
triggers:
- issues
tools:
- code-search
- issue-update
- comment-create
permissions:
issues: write
contents: read
safe-outputs: false
---
# Issue Triager
When a new issue is created:
1. Read the issue title and body
2. Classify the issue type: bug, feature-request, question, documentation
3. Determine the affected component by searching the codebase for related files
4. Apply appropriate labels
5. If the issue is a bug, add priority label based on severity
6. Post a welcome comment acknowledging the issue and explaining next stepsExample 3: Documentation Updater
---
name: doc-updater
description: Updates documentation when code changes
triggers:
- type: push
branches: [main]
paths: ['src/api/**']
tools:
- code-search
- file-read
- file-write
- comment-create
permissions:
contents: write
pull-requests: write
safe-outputs: true
---
# Documentation Updater
When API code changes are pushed to main:
1. Identify which API endpoints were modified
2. Read the current documentation in docs/api/
3. Compare code with documentation to find discrepancies
4. Create a PR with documentation updates
5. Include a summary of what changed and why
## Rules
- Only update documentation that is directly affected by code changes
- Preserve existing documentation style and formatting
- Add examples for new endpoints
- Mark deprecated endpoints clearly---
Integration with Traditional GitHub Actions
Agentic workflows coexist with GitHub Actions. Common integration patterns:
Pattern 1: Agent Triggered by Action
# .github/workflows/on-pr.yml
on:
pull_request:
jobs:
lint:
runs-on: ubuntu-latest
steps:
- run: make lint
# The agentic workflow in .github/agents/code-review.md
# fires on the same pull_request event independentlyPattern 2: Agent Triggers an Action
---
tools:
- workflow-trigger
permissions:
actions: write
---
If the review finds critical security issues, trigger the
security-alert workflow to notify the security team.Pattern 3: Action Passes Context to Agent
GitHub Actions can create comments or artifacts that agentic workflows read as context for more informed analysis.
---
Best Practices
1. Start with safe-outputs enabled. Disable only after building trust with a specific workflow. 2. Scope permissions minimally. Give each workflow only the tools and permissions it needs. 3. Write clear, structured instructions. Use numbered steps and explicit criteria. 4. Test with `gh aw run` before enabling triggers. Validate behavior on real PRs/issues. 5. Set reasonable timeouts. Default 15 minutes is good for most tasks; reduce for simple triage. 6. Monitor execution logs. Review gh aw logs regularly to catch unexpected behavior. 7. Version your workflows. Track changes to .github/agents/ in version control like any other config. 8. Combine with Actions for deterministic gates. Use Actions for build/test/deploy; use agents for review/triage/documentation. 9. Keep instructions focused. One workflow per task. Do not create a single workflow that tries to do everything. 10. Document the expected behavior. Include a "## Rules" section in the markdown to set explicit boundaries.
---
Limitations and Considerations
- Non-deterministic: Agentic workflows may produce different outputs for the same input. Use safe-outputs and human review.
- Token costs: Each execution consumes AI model tokens. Monitor usage for cost control.
- Rate limits: Frequent triggers (every push, every comment) can hit rate limits. Use path filters and concurrency groups.
- Not a replacement for Actions: Deterministic tasks (build, test, deploy) should remain in GitHub Actions.
- Model availability: Execution depends on AI model availability. Plan for occasional delays or failures.
- Repository access: Agentic workflows can only access the repository they are defined in (no cross-repo access by default).
Deployment Strategies
Comprehensive guide to deployment strategies for production systems. Covers blue-green, canary, rolling, A/B testing, feature flags, and database migration handling during deployments.
---
Table of Contents
1. Strategy Comparison 2. Blue-Green Deployment 3. Canary Deployment 4. Rolling Deployment 5. A/B Testing Deployments 6. Feature Flags 7. Database Migrations During Deploy 8. Choosing the Right Strategy
---
Strategy Comparison
| Aspect | Blue-Green | Canary | Rolling | A/B Testing |
|---|---|---|---|---|
| Zero downtime | Yes | Yes | Yes | Yes |
| Rollback speed | Instant | Instant | Minutes | Instant |
| Infrastructure cost | 2x during deploy | 1.1-1.5x | 1x | 1.1-1.5x |
| Complexity | Low | High | Low | High |
| Production testing | Full env before switch | Gradual real traffic | Mixed versions | Segment-based |
| Best for | Critical apps | High-traffic services | Stateless services | UX experiments |
| Risk level | Low | Very low | Medium | Very low |
---
Blue-Green Deployment
Overview
Blue-green deployment maintains two identical production environments. At any time, only one (say "blue") serves live traffic. New versions are deployed to the inactive environment ("green"), validated, and then traffic is switched.
Architecture
Load Balancer
/ \
/ \
[Blue - v1.2] [Green - v1.3]
(ACTIVE) (STAGING)
\ /
\ /
Shared DatabaseImplementation Steps
1. Deploy to green environment
# Deploy new version to green
kubectl apply -f deployment-green.yaml
kubectl rollout status deployment/app-green --timeout=300s2. Run validation on green
# Health checks
curl -sf https://green.internal.example.com/health || exit 1
# Smoke tests against green
SMOKE_URL=https://green.internal.example.com pytest tests/smoke/3. Switch traffic
# Update load balancer to point to green
# AWS ALB example:
aws elbv2 modify-listener --listener-arn $LISTENER_ARN \
--default-actions Type=forward,TargetGroupArn=$GREEN_TG_ARN
# Or Kubernetes service selector:
kubectl patch service app-service -p '{"spec":{"selector":{"version":"green"}}}'4. Monitor
- Watch error rates for 15-30 minutes.
- Compare latency baselines.
- Verify business metrics (conversion, API success rate).
5. Finalize or rollback
# If healthy: decommission blue (or keep as rollback)
# If unhealthy: switch back to blue
kubectl patch service app-service -p '{"spec":{"selector":{"version":"blue"}}}'GitHub Actions Integration
jobs:
deploy-green:
environment: production-green
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Deploy to green
run: ./scripts/deploy.sh green ${{ github.sha }}
- name: Validate green
run: ./scripts/validate.sh green
switch-traffic:
needs: deploy-green
environment: production
runs-on: ubuntu-latest
steps:
- name: Switch traffic to green
run: ./scripts/switch-traffic.sh green
- name: Monitor (5 min)
run: ./scripts/monitor.sh --duration 300 --threshold 0.01When to Use
- Applications where instant rollback is critical.
- Systems that can afford 2x infrastructure during deployments.
- When you need to validate the full environment before exposing to users.
When to Avoid
- Very large infrastructure (cost doubles during deploy).
- Stateful services with data that diverges between environments.
- When database schema changes require both versions to coexist.
---
Canary Deployment
Overview
Canary deployment routes a small fraction of traffic to the new version while the majority continues on the stable version. Traffic percentage increases gradually based on success metrics.
Architecture
Load Balancer / Service Mesh
/ | \
95% | 5%
/ | \
[Stable v1.2] | [Canary v1.3]
(10 replicas) | (1 replica)
|
Metric Collection
(errors, latency)Traffic Split Schedule
| Phase | Canary % | Duration | Promotion Gate |
|---|---|---|---|
| Deploy | 0% | 5 min | Canary pods healthy |
| Phase 1 | 5% | 15 min | Error rate < 0.1%, P99 < 200ms |
| Phase 2 | 25% | 30 min | Error rate < 0.1%, P99 < 200ms |
| Phase 3 | 50% | 60 min | All metrics within baseline |
| Phase 4 | 100% | -- | Full promotion |
Implementation with Kubernetes
Using Argo Rollouts:
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
name: my-app
spec:
replicas: 10
strategy:
canary:
steps:
- setWeight: 5
- pause: { duration: 15m }
- setWeight: 25
- pause: { duration: 30m }
- setWeight: 50
- pause: { duration: 60m }
- setWeight: 100
canaryMetadata:
labels:
role: canary
stableMetadata:
labels:
role: stable
analysis:
templates:
- templateName: success-rate
startingStep: 1
args:
- name: service-name
value: my-appAnalysis Template
apiVersion: argoproj.io/v1alpha1
kind: AnalysisTemplate
metadata:
name: success-rate
spec:
args:
- name: service-name
metrics:
- name: success-rate
interval: 1m
successCondition: result[0] >= 0.99
failureCondition: result[0] < 0.95
failureLimit: 3
provider:
prometheus:
address: http://prometheus:9090
query: |
sum(rate(http_requests_total{service="{{args.service-name}}",status!~"5.."}[5m]))
/
sum(rate(http_requests_total{service="{{args.service-name}}"}[5m]))Implementation with Istio
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: my-app
spec:
hosts:
- my-app
http:
- route:
- destination:
host: my-app
subset: stable
weight: 95
- destination:
host: my-app
subset: canary
weight: 5When to Use
- High-traffic services where a bad deploy affects many users.
- When you have good observability and can detect issues at low traffic percentages.
- Services where you want real production validation before full rollout.
When to Avoid
- Low-traffic services (not enough signal at 5%).
- Services without good metrics/monitoring.
- When speed of deployment is more important than safety.
---
Rolling Deployment
Overview
Rolling deployment replaces instances of the old version with the new version one at a time (or in configured batch sizes). At least some instances are always available.
Architecture
Time T0: [v1] [v1] [v1] [v1] (all old)
Time T1: [v2] [v1] [v1] [v1] (1 updated)
Time T2: [v2] [v2] [v1] [v1] (2 updated)
Time T3: [v2] [v2] [v2] [v1] (3 updated)
Time T4: [v2] [v2] [v2] [v2] (all new)Kubernetes Configuration
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
replicas: 4
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1 # Max extra pods during update
maxUnavailable: 0 # Always maintain full capacity
template:
spec:
containers:
- name: app
image: my-app:v2
readinessProbe:
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 15
periodSeconds: 10Key Parameters
| Parameter | Description | Recommended Default |
|---|---|---|
maxSurge | Extra pods allowed during update | 25% or 1 |
maxUnavailable | Pods that can be unavailable | 0 (safest) or 25% |
minReadySeconds | Wait after ready before next | 10-30 seconds |
progressDeadlineSeconds | Timeout for rollout | 600 seconds |
Rollback
# Automatic rollback on failure
kubectl rollout undo deployment/my-app
# Rollback to specific revision
kubectl rollout undo deployment/my-app --to-revision=3
# Check rollout history
kubectl rollout history deployment/my-appWhen to Use
- Stateless services with multiple replicas.
- Kubernetes-native deployments.
- When infrastructure cost must stay flat (no extra environment).
When to Avoid
- When both versions cannot serve traffic simultaneously (breaking API changes).
- Single-instance deployments (no redundancy during update).
- When instant rollback is required (rolling back is another rolling update).
---
A/B Testing Deployments
Overview
A/B testing deployments route traffic to different versions based on user attributes (not random traffic splitting). This enables data-driven decisions about which version performs better.
Difference from Canary
| Aspect | Canary | A/B Test |
|---|---|---|
| Goal | Risk mitigation | Feature validation |
| Routing | Random percentage | User attributes |
| Duration | Hours | Days to weeks |
| Metrics | Error rate, latency | Business KPIs |
| Rollback trigger | Technical failure | Experiment conclusion |
Routing Strategies
Header-based:
# Istio VirtualService
http:
- match:
- headers:
x-experiment-group:
exact: "variant-b"
route:
- destination:
host: my-app
subset: variant-b
- route:
- destination:
host: my-app
subset: variant-aCookie-based:
http:
- match:
- headers:
cookie:
regex: ".*ab_group=B.*"
route:
- destination:
host: my-app
subset: variant-bUser-segment based (application level):
def get_variant(user):
"""Determine which variant to show based on user attributes."""
if user.id % 100 < 50:
return "A"
return "B"Metrics to Track
- Primary metric: Conversion rate, revenue per user, engagement time.
- Guardrail metrics: Error rate, latency, bounce rate (must not degrade).
- Statistical significance: Typically need p < 0.05 with adequate sample size.
When to Use
- Validating new UI/UX against existing.
- Testing pricing changes.
- Comparing different recommendation algorithms.
---
Feature Flags
Overview
Feature flags decouple deployment from release. Code is deployed but features are toggled on/off independently, enabling gradual rollout, instant kill switches, and A/B testing without redeployment.
Flag Types
| Type | Lifespan | Example |
|---|---|---|
| Release flag | Days to weeks | New checkout flow |
| Experiment flag | Weeks to months | Pricing experiment |
| Ops flag | Permanent | Maintenance mode, rate limits |
| Permission flag | Permanent | Premium features, beta access |
Implementation Patterns
Simple boolean flag:
def checkout(request):
if flags.is_enabled("new-checkout-v2"):
return new_checkout(request)
return legacy_checkout(request)Percentage rollout:
def checkout(request):
if flags.is_enabled("new-checkout-v2", percentage=10):
return new_checkout(request)
return legacy_checkout(request)User-targeted flag:
def checkout(request):
if flags.is_enabled("new-checkout-v2", user=request.user,
rules={"plan": "premium", "country": "US"}):
return new_checkout(request)
return legacy_checkout(request)Flag Lifecycle
1. Create flag (disabled) -----> Code deploys with flag check
2. Enable for internal users --> Test in production
3. Enable for 5% of users ----> Monitor metrics
4. Increase to 25%, 50% ------> Gradual rollout
5. Enable for 100% -----------> Full release
6. Remove flag from code ------> Clean up (critical step!)Best Practices
- Name flags clearly:
enable-new-checkout-flownotflag-123. - Set expiration dates: Stale flags are tech debt.
- Log flag evaluations: Know which users see which variant.
- Have a kill switch process: Document how to disable a flag in an emergency.
- Clean up after rollout: Remove flag code once fully launched.
- Limit active flags: More than 10-15 active flags creates combinatorial complexity.
Feature Flag in GitHub Actions
jobs:
deploy:
steps:
- name: Set feature flags for environment
run: |
# Using a hypothetical flag management CLI
flag-ctl set new-checkout-v2 \
--env ${{ inputs.environment }} \
--percentage 5 \
--description "Gradual rollout of new checkout"Services
| Service | Type | Key Feature |
|---|---|---|
| LaunchDarkly | SaaS | Real-time flag evaluation, experimentation |
| Unleash | Open source | Self-hosted, Kubernetes-native |
| Flagsmith | Open source + SaaS | Remote config + flags |
| Split.io | SaaS | Feature flags + experimentation platform |
| Custom | DIY | Simple JSON/YAML config file |
---
Database Migrations During Deploy
The Challenge
Database schema changes during deployment create risk because:
- Old code may not work with new schema.
- New code may not work with old schema.
- Both versions run simultaneously during rolling/canary/blue-green deployments.
Golden Rule: Backward-Compatible Migrations
Every migration must be compatible with both the current and previous version of the application code.
Safe Migration Patterns
1. Add a column (safe)
-- Migration: Add column with default
ALTER TABLE users ADD COLUMN email_verified BOOLEAN DEFAULT FALSE;
-- Old code ignores the new column. New code uses it.2. Remove a column (two-phase)
Phase 1 (deploy v2): Stop reading the column in code. Deploy.
Phase 2 (deploy v3): Drop the column in migration. Deploy.3. Rename a column (three-phase)
Phase 1: Add new column, write to both old and new.
Phase 2: Migrate data, read from new column.
Phase 3: Drop old column.4. Add an index (safe, but watch performance)
-- Use CONCURRENTLY to avoid locking the table
CREATE INDEX CONCURRENTLY idx_users_email ON users(email);5. Change column type (two-phase)
Phase 1: Add new column with new type, dual-write.
Phase 2: Migrate data, switch reads to new column.
Phase 3: Drop old column.Unsafe Migration Patterns (Avoid)
| Migration | Risk | Safe Alternative |
|---|---|---|
| DROP COLUMN | Old code breaks | Two-phase: stop using, then drop |
| RENAME COLUMN | Both versions break | Three-phase: add, migrate, drop |
| ALTER TYPE (in-place) | Table lock, old code breaks | Add new column, migrate |
| NOT NULL without default | INSERT fails for old code | Add with DEFAULT first |
| DROP TABLE | Everything breaks | Deprecate, stop references, then drop |
Migration Execution in CI/CD
jobs:
migrate:
runs-on: ubuntu-latest
environment: ${{ inputs.environment }}
steps:
- uses: actions/checkout@v4
- name: Backup database
run: ./scripts/db-backup.sh ${{ inputs.environment }}
- name: Run migration (dry run)
run: ./scripts/db-migrate.sh --dry-run --env ${{ inputs.environment }}
- name: Run migration
run: ./scripts/db-migrate.sh --env ${{ inputs.environment }}
- name: Verify migration
run: ./scripts/db-verify.sh --env ${{ inputs.environment }}
deploy:
needs: migrate
# ... deploy application codeMigration Rollback
- Always write a corresponding
downmigration. - Test rollback in staging before deploying to production.
- If a migration is not reversible, document it and plan accordingly.
# Example: Django migration with reverse
class Migration(migrations.Migration):
operations = [
migrations.AddField(
model_name='user',
name='email_verified',
field=models.BooleanField(default=False),
),
]
# Django auto-generates reverse: RemoveField---
Choosing the Right Strategy
Decision Tree
Start
|
v
Is zero-downtime required?
|
No --> Simple deployment (stop old, start new)
|
Yes
|
v
Is instant rollback critical?
|
No --> Rolling deployment
|
Yes
|
v
Can you afford 2x infrastructure?
|
Yes --> Blue-green deployment
|
No
|
v
Do you have good observability (metrics, alerting)?
|
No --> Blue-green (simpler to manage)
|
Yes
|
v
Is traffic high enough for meaningful canary signal?
|
No --> Blue-green
|
Yes --> Canary deploymentStrategy Selection by Service Type
| Service Type | Recommended Primary | Recommended Secondary |
|---|---|---|
| User-facing web app | Blue-green | Canary |
| API service | Canary | Rolling |
| Background worker | Rolling | Blue-green |
| Stateful service | Blue-green | Rolling (careful) |
| Database | Rolling (with migrations) | N/A |
| Shared library | Version publish | N/A |
| Mobile app | Staged rollout (canary) | Feature flags |
| Infrastructure | Rolling (Terraform) | Blue-green |
Combining Strategies
Production deployments often combine multiple strategies:
1. Feature flags + Canary: Deploy with feature hidden behind flag, canary the deployment, then gradually enable the flag. 2. Blue-green + Database migration: Run backward-compatible migration first, then blue-green switch the application. 3. Rolling + Health checks: Standard Kubernetes rolling update with readiness probes acting as automatic gates. 4. Canary + A/B test: Use canary for risk mitigation during deploy, then A/B test the feature over days.
GitHub Actions Patterns
30+ proven patterns for production GitHub Actions workflows. Each pattern includes a working example and guidance on when to apply it.
---
Table of Contents
1. Trigger Patterns 2. Job Structure Patterns 3. Matrix Patterns 4. Caching Patterns 5. Artifact Patterns 6. Security Patterns 7. Reusable Workflow Patterns 8. Composite Action Patterns 9. Environment Patterns 10. Advanced Patterns
---
Trigger Patterns
Pattern 1: Path-Filtered Push
Run the workflow only when relevant files change.
on:
push:
branches: [main]
paths:
- 'src/**'
- 'tests/**'
- 'Dockerfile'
- 'requirements*.txt'
paths-ignore:
- '**.md'
- 'docs/**'When to use: Reduce unnecessary CI runs on documentation-only or config-only changes.
Pattern 2: Manual Dispatch with Inputs
Allow manual triggering with parameters.
on:
workflow_dispatch:
inputs:
environment:
description: 'Target environment'
required: true
type: choice
options: [dev, staging, production]
dry_run:
description: 'Dry run (no actual deploy)'
required: false
type: boolean
default: true
version:
description: 'Version to deploy (leave empty for latest)'
required: false
type: stringWhen to use: On-demand deployments, maintenance tasks, or any workflow that needs human-supplied parameters.
Pattern 3: Scheduled with Conditional Skip
Run on schedule but skip if there are no new changes.
on:
schedule:
- cron: '0 6 * * 1-5' # Weekdays at 6 AM UTC
jobs:
check:
runs-on: ubuntu-latest
outputs:
should_run: ${{ steps.check.outputs.should_run }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 2
- id: check
run: |
if git diff --quiet HEAD~1; then
echo "should_run=false" >> "$GITHUB_OUTPUT"
else
echo "should_run=true" >> "$GITHUB_OUTPUT"
fi
build:
needs: check
if: needs.check.outputs.should_run == 'true'
runs-on: ubuntu-latest
steps:
- run: echo "Building..."When to use: Nightly dependency checks or security scans that should skip if nothing changed.
Pattern 4: PR Target Branch Filter
Run different checks depending on the target branch of a PR.
on:
pull_request:
branches:
- main
- 'release/**'
jobs:
basic-checks:
runs-on: ubuntu-latest
steps:
- run: echo "Run on all PRs"
release-checks:
if: startsWith(github.base_ref, 'release/')
runs-on: ubuntu-latest
steps:
- run: echo "Extra checks for release branches"When to use: Enforce stricter validation for release branch PRs.
Pattern 5: Multi-Event Trigger with Conditional Logic
Handle multiple triggers with event-specific behavior.
on:
push:
branches: [main]
pull_request:
branches: [main]
release:
types: [published]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: make build
- name: Publish (release only)
if: github.event_name == 'release'
run: make publish---
Job Structure Patterns
Pattern 6: Fan-Out / Fan-In
Run independent checks in parallel, then merge results.
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: make lint
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: make test
security:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: make security-scan
gate:
needs: [lint, test, security]
runs-on: ubuntu-latest
steps:
- run: echo "All checks passed"When to use: Always. This is the default pattern for CI pipelines.
Pattern 7: Conditional Job Execution
Skip jobs based on commit message or labels.
jobs:
test:
if: "!contains(github.event.head_commit.message, '[skip ci]')"
runs-on: ubuntu-latest
steps:
- run: make test
deploy:
if: contains(github.event.pull_request.labels.*.name, 'deploy-preview')
runs-on: ubuntu-latest
steps:
- run: make deploy-previewPattern 8: Job Output Passing
Pass data from one job to another.
jobs:
prepare:
runs-on: ubuntu-latest
outputs:
version: ${{ steps.version.outputs.version }}
sha_short: ${{ steps.sha.outputs.sha_short }}
steps:
- uses: actions/checkout@v4
- id: version
run: echo "version=$(cat VERSION)" >> "$GITHUB_OUTPUT"
- id: sha
run: echo "sha_short=$(git rev-parse --short HEAD)" >> "$GITHUB_OUTPUT"
build:
needs: prepare
runs-on: ubuntu-latest
steps:
- run: echo "Building version ${{ needs.prepare.outputs.version }}"Pattern 9: Timeout and Retry
Protect against hung processes and handle transient failures.
jobs:
test:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v4
- name: Run flaky integration test (with retry)
uses: nick-fields/retry@v3
with:
timeout_minutes: 5
max_attempts: 3
command: make integration-test---
Matrix Patterns
Pattern 10: Basic Cross-Platform Matrix
Test across operating systems and language versions.
jobs:
test:
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
python-version: ['3.10', '3.11', '3.12']
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- run: python -m pytestPattern 11: Matrix with Include/Exclude
Fine-tune matrix combinations.
strategy:
matrix:
os: [ubuntu-latest, macos-latest]
node: [18, 20, 22]
exclude:
- os: macos-latest
node: 18
include:
- os: ubuntu-latest
node: 22
coverage: trueWhen to use: Skip combinations that are not supported or add extra configuration to specific combinations.
Pattern 12: Dynamic Matrix from JSON
Generate matrix values dynamically.
jobs:
generate-matrix:
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.set.outputs.matrix }}
steps:
- uses: actions/checkout@v4
- id: set
run: |
# Read from file, API, or compute dynamically
MATRIX=$(python generate_matrix.py)
echo "matrix=$MATRIX" >> "$GITHUB_OUTPUT"
build:
needs: generate-matrix
strategy:
matrix: ${{ fromJson(needs.generate-matrix.outputs.matrix) }}
runs-on: ${{ matrix.runner }}
steps:
- run: echo "Building ${{ matrix.name }}"Pattern 13: Matrix with Shared Setup
Factor out common setup into a composite action or reusable step.
jobs:
test:
strategy:
matrix:
shard: [1, 2, 3, 4]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/setup-project
- name: Run test shard
run: pytest --shard-id=${{ matrix.shard }} --num-shards=4---
Caching Patterns
Pattern 14: Language-Native Cache
Use built-in cache support in setup actions.
# Python
- uses: actions/setup-python@v5
with:
python-version: '3.12'
cache: pip
# Node.js
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
# Go
- uses: actions/setup-go@v5
with:
go-version: '1.22'
cache: truePattern 15: Multi-Path Cache
Cache multiple directories with a composite key.
- uses: actions/cache@v4
with:
path: |
~/.cache/pip
~/.local/share/virtualenvs
.mypy_cache
.pytest_cache
key: ${{ runner.os }}-full-${{ hashFiles('**/requirements*.txt', 'setup.cfg') }}
restore-keys: |
${{ runner.os }}-full-
${{ runner.os }}-Pattern 16: Docker Layer Cache with BuildKit
Cache Docker build layers using GitHub Actions cache backend.
- uses: docker/build-push-action@v5
with:
context: .
push: true
tags: ${{ env.REGISTRY }}/${{ env.IMAGE }}:${{ github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=maxPattern 17: Turbo/Nx Build Cache
Cache build outputs for monorepo build systems.
- uses: actions/cache@v4
with:
path: node_modules/.cache/turbo
key: turbo-${{ runner.os }}-${{ hashFiles('**/turbo.json', '**/package-lock.json') }}
restore-keys: turbo-${{ runner.os }}----
Artifact Patterns
Pattern 18: Build Once, Deploy Everywhere
Build an artifact in one job, use it across deployment jobs.
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci && npm run build
- uses: actions/upload-artifact@v4
with:
name: dist
path: dist/
retention-days: 1
deploy-staging:
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@v4
with:
name: dist
path: dist/
- run: ./deploy.sh staging dist/
deploy-prod:
needs: deploy-staging
runs-on: ubuntu-latest
environment: production
steps:
- uses: actions/download-artifact@v4
with:
name: dist
path: dist/
- run: ./deploy.sh production dist/Pattern 19: Test Report Artifacts
Aggregate test results across matrix jobs.
- uses: actions/upload-artifact@v4
if: always()
with:
name: test-results-${{ matrix.os }}-${{ matrix.version }}
path: |
**/junit-*.xml
**/coverage-*.xml
retention-days: 7---
Security Patterns
Pattern 20: OIDC Authentication (AWS)
Use short-lived tokens instead of stored credentials.
permissions:
id-token: write
contents: read
steps:
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/github-actions
aws-region: us-east-1Pattern 21: OIDC Authentication (GCP)
permissions:
id-token: write
contents: read
steps:
- uses: google-github-actions/auth@v2
with:
workload_identity_provider: projects/123456/locations/global/workloadIdentityPools/github/providers/my-repo
service_account: github-actions@my-project.iam.gserviceaccount.comPattern 22: Minimal Permissions
Always declare the minimum permissions needed.
# Top-level: restrict all
permissions:
contents: read
jobs:
deploy:
permissions:
contents: read
packages: write # Only this job needs package write
id-token: write # Only this job needs OIDCPattern 23: Secret Masking for Dynamic Values
Mask dynamically generated sensitive values.
steps:
- name: Generate token
id: token
run: |
TOKEN=$(curl -s https://auth.example.com/token)
echo "::add-mask::$TOKEN"
echo "token=$TOKEN" >> "$GITHUB_OUTPUT"Pattern 24: Dependency Review on PRs
Block PRs that introduce vulnerable dependencies.
on:
pull_request:
jobs:
dependency-review:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/dependency-review-action@v4
with:
fail-on-severity: high
deny-licenses: GPL-3.0, AGPL-3.0---
Reusable Workflow Patterns
Pattern 25: Parameterized Reusable Workflow
Define a workflow that can be called with different parameters.
# .github/workflows/reusable-test.yml
on:
workflow_call:
inputs:
python-version:
type: string
default: '3.12'
test-command:
type: string
default: 'pytest'
secrets:
CODECOV_TOKEN:
required: false
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: ${{ inputs.python-version }}
cache: pip
- run: pip install -r requirements.txt
- run: ${{ inputs.test-command }}Pattern 26: Chained Reusable Workflows
Call reusable workflows in sequence.
jobs:
test:
uses: ./.github/workflows/reusable-test.yml
with:
python-version: '3.12'
build:
needs: test
uses: ./.github/workflows/reusable-build.yml
with:
push_image: true
secrets: inherit
deploy:
needs: build
uses: ./.github/workflows/reusable-deploy.yml
with:
environment: staging
secrets: inheritPattern 27: Cross-Repository Reusable Workflow
Call a workflow from another repository.
jobs:
deploy:
uses: my-org/shared-workflows/.github/workflows/deploy.yml@v2
with:
environment: production
secrets:
DEPLOY_KEY: ${{ secrets.DEPLOY_KEY }}---
Composite Action Patterns
Pattern 28: Project Setup Composite
Bundle common setup steps.
# .github/actions/setup-project/action.yml
name: Setup Project
description: Install all dependencies and configure environment
inputs:
python-version:
default: '3.12'
runs:
using: composite
steps:
- uses: actions/setup-python@v5
with:
python-version: ${{ inputs.python-version }}
cache: pip
- run: pip install -r requirements.txt -r requirements-dev.txt
shell: bash
- run: pre-commit install
shell: bashPattern 29: Deploy Composite with Outputs
A composite action that deploys and returns the deployment URL.
# .github/actions/deploy/action.yml
name: Deploy
description: Deploy to environment and return URL
inputs:
environment:
required: true
image_tag:
required: true
outputs:
url:
description: Deployment URL
value: ${{ steps.deploy.outputs.url }}
runs:
using: composite
steps:
- id: deploy
run: |
URL=$(./deploy.sh ${{ inputs.environment }} ${{ inputs.image_tag }})
echo "url=$URL" >> "$GITHUB_OUTPUT"
shell: bash---
Environment Patterns
Pattern 30: Environment Protection Rules
Use GitHub environments with required reviewers and wait timers.
jobs:
deploy-prod:
environment:
name: production
url: https://myapp.example.com
runs-on: ubuntu-latest
steps:
- run: ./deploy.sh productionConfigure in Settings > Environments > production:
- Required reviewers: team leads
- Wait timer: 5 minutes
- Deployment branches: only
main
Pattern 31: Environment-Specific Secrets and Variables
Use environment-scoped configuration.
jobs:
deploy:
environment: ${{ inputs.environment }}
runs-on: ubuntu-latest
steps:
- run: |
curl -X POST ${{ vars.DEPLOY_URL }} \
-H "Authorization: Bearer ${{ secrets.DEPLOY_TOKEN }}" \
-d '{"version": "${{ github.sha }}"}'Each environment (dev, staging, production) has its own DEPLOY_URL variable and DEPLOY_TOKEN secret.
---
Advanced Patterns
Pattern 32: Concurrency Groups
Prevent concurrent runs and cancel outdated ones.
concurrency:
group: deploy-${{ github.ref }}
cancel-in-progress: trueFor production deployments, do NOT cancel in progress:
concurrency:
group: deploy-production
cancel-in-progress: falsePattern 33: Step Summary
Write rich markdown summaries visible in the Actions UI.
- name: Generate summary
run: |
echo "## Build Results" >> "$GITHUB_STEP_SUMMARY"
echo "| Metric | Value |" >> "$GITHUB_STEP_SUMMARY"
echo "|--------|-------|" >> "$GITHUB_STEP_SUMMARY"
echo "| Tests | 142 passed |" >> "$GITHUB_STEP_SUMMARY"
echo "| Coverage | 87.3% |" >> "$GITHUB_STEP_SUMMARY"
echo "| Build time | 3m 12s |" >> "$GITHUB_STEP_SUMMARY"Pattern 34: PR Comment with Results
Post CI results as a PR comment.
- uses: marocchino/sticky-pull-request-comment@v2
with:
header: ci-results
message: |
## CI Results
- Tests: ${{ steps.test.outputs.result }}
- Coverage: ${{ steps.coverage.outputs.percentage }}%
- Build: ${{ steps.build.outputs.status }}Pattern 35: Monorepo Path-Based Jobs
Run only the jobs relevant to changed packages.
jobs:
changes:
runs-on: ubuntu-latest
outputs:
frontend: ${{ steps.filter.outputs.frontend }}
backend: ${{ steps.filter.outputs.backend }}
infra: ${{ steps.filter.outputs.infra }}
steps:
- uses: actions/checkout@v4
- uses: dorny/paths-filter@v3
id: filter
with:
filters: |
frontend:
- 'packages/frontend/**'
backend:
- 'packages/backend/**'
infra:
- 'terraform/**'
test-frontend:
needs: changes
if: needs.changes.outputs.frontend == 'true'
runs-on: ubuntu-latest
steps:
- run: cd packages/frontend && npm test
test-backend:
needs: changes
if: needs.changes.outputs.backend == 'true'
runs-on: ubuntu-latest
steps:
- run: cd packages/backend && pytestPattern 36: Automatic PR Labeling
Label PRs based on changed files.
on:
pull_request:
jobs:
label:
runs-on: ubuntu-latest
permissions:
pull-requests: write
steps:
- uses: actions/labeler@v5
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}With .github/labeler.yml:
frontend:
- changed-files:
- any-glob-to-any-file: 'src/frontend/**'
backend:
- changed-files:
- any-glob-to-any-file: 'src/backend/**'
documentation:
- changed-files:
- any-glob-to-any-file: '**/*.md'Pattern 37: Release Drafter
Automatically draft release notes from PR titles.
on:
push:
branches: [main]
pull_request:
types: [opened, reopened, synchronize]
permissions:
contents: read
pull-requests: write
jobs:
draft:
runs-on: ubuntu-latest
steps:
- uses: release-drafter/release-drafter@v6
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}CI/CD Workflows, Strategies, and Optimization
Read this when designing a CI or CD pipeline, choosing a deployment strategy, optimizing pipeline cost/runtime, applying GitHub Actions patterns, or troubleshooting workflows.
Workflow 1: CI Pipeline Design
The agent generates pipelines following fail-fast ordering:
1. Lint and format (~30s) -- cheapest gate first 2. Unit tests (~2-5m) -- matrix across versions 3. Build verification (~3-8m) 4. Integration tests (~5-15m, parallel with build) 5. Security scanning (~2-5m)
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: make lint
test:
needs: lint
strategy:
matrix:
python-version: ['3.10', '3.11', '3.12']
steps:
- uses: actions/setup-python@v5
with: { python-version: "${{ matrix.python-version }}", cache: pip }
- run: pip install -r requirements.txt
- run: pytest --junitxml=results.xml
security:
needs: lint
steps:
- run: pip-audit -r requirements.txtCI targets:
| Metric | Target | Fix |
|---|---|---|
| Total CI time | < 10 min | Parallelize, add caching |
| Lint step | < 1 min | Use pre-commit locally |
| Unit tests | < 5 min | Split suites, use matrix |
| Flaky rate | < 1% | Quarantine flaky tests |
| Cache hit rate | > 80% | Review cache keys |
Workflow 2: CD Pipeline and Multi-Environment Deployment
python scripts/deployment_planner.py --type webapp --environments dev,staging,prod --format jsonEnvironment promotion flow:
Build -> Dev (auto) -> Staging (auto) -> Production (manual approval)
|
Canary (10%) -> Full rollout| Aspect | Dev | Staging | Production |
|---|---|---|---|
| Trigger | Every push | Merge to main | Manual approval |
| Replicas | 1 | 2 | 3+ (auto-scaled) |
| Secrets | Repository | Environment | Vault/OIDC |
| Monitoring | Basic logs | Full observability | Full + alerting |
Key CD rules:
- Build once, deploy the same artifact everywhere
- Tag artifacts with commit SHA for traceability
- Use environment protection rules for production gates
- Maintain rollback capability at every stage
Workflow 3: Pipeline Optimization
python scripts/pipeline_analyzer.py .github/workflows/ --format json -o report.jsonThe agent checks for:
1. Missing caching -- dependencies reinstalled every run 2. No timeouts -- stuck jobs burn budget 3. Sequential chains that could parallelize 4. Deprecated actions with newer versions available 5. Security issues -- secrets in logs, missing permissions scoping 6. Cost inefficiency -- oversized runners, no path filtering
Optimization techniques:
Path-based filtering -- skip CI for docs-only changes:
on:
push:
paths: ['src/**', 'tests/**', 'requirements*.txt']
paths-ignore: ['docs/**', '*.md']Concurrency cancellation -- cancel superseded runs:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: trueDependency caching:
- uses: actions/cache@v4
with:
path: ~/.cache/pip
key: ${{ runner.os }}-deps-${{ hashFiles('**/requirements.txt') }}Deployment Strategies
Decision tree:
Zero-downtime required?
No -> Rolling deployment
Yes -> Need instant rollback?
No -> Rolling with health checks
Yes -> Budget for 2x infrastructure?
Yes -> Blue-green
No -> CanaryCanary traffic split schedule:
| Phase | % | Duration | Gate |
|---|---|---|---|
| 1 | 5% | 15 min | Error rate < 0.1% |
| 2 | 25% | 30 min | P99 latency < 200ms |
| 3 | 50% | 60 min | Business metrics stable |
| 4 | 100% | -- | Full promotion |
GitHub Actions Patterns
Reusable workflows -- define once, call everywhere:
# .github/workflows/reusable-deploy.yml
on:
workflow_call:
inputs:
environment: { required: true, type: string }
image_tag: { required: true, type: string }
secrets:
DEPLOY_KEY: { required: true }OIDC authentication -- no long-lived credentials:
permissions:
id-token: write
contents: read
steps:
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789:role/github-actions
aws-region: us-east-1Secrets hierarchy: Organization > Repository > Environment. Never echo secrets; use add-mask for dynamic values. Prefer OIDC for cloud auth.
Runner Cost Optimization
| Runner | vCPU | RAM | Cost/min | Best For |
|---|---|---|---|---|
| 2-core | 2 | 7 GB | $0.008 | Standard tasks |
| 4-core | 4 | 16 GB | $0.016 | Build-heavy |
| 8-core | 8 | 32 GB | $0.032 | Large compilations |
| 16-core | 16 | 64 GB | $0.064 | Parallel test suites |
Monthly estimate: (runs/day) x (avg min/run) x 30 x (cost/min) Example: 50 pushes/day x 8 min x 30 = 12,000 min x $0.008 = $96/month.
Anti-Patterns
| Anti-Pattern | Problem | Fix |
|---|---|---|
| Monolithic workflow | 45-min single workflow | Split into parallel jobs |
| No caching | Reinstall deps every run | Cache dependencies and builds |
| Secrets in logs | Leaked credentials | add-mask, avoid echo |
| No timeout | Stuck jobs burn budget | timeout-minutes on every job |
| Full matrix every push | 30-min matrix on every commit | Full nightly; reduced on push |
| No rollback plan | Stuck with broken deploy | Automate rollback in CD pipeline |
Troubleshooting
| Problem | Cause | Solution |
|---|---|---|
| Workflow never triggers | Wrong on: config or branch name mismatch | Verify triggers match branching strategy |
| Cache miss every run | Volatile cache key (timestamp) | Use hashFiles() on lock files |
| Matrix fails on one OS only | Platform-specific paths or deps | Use shell: bash; install OS deps per matrix entry |
| Secret not available | Wrong environment scope | Ensure job declares correct environment: |
| Health check fails after deploy | App not started before check | Add retry loop with backoff |
| Concurrency cancels needed runs | Overly broad group key | Scope to workflow-ref; separate groups for deploy |
#!/usr/bin/env python3
"""
Deployment Planner
Generates a deployment plan document based on project type, target environments,
and deployment strategy. Includes environment matrix, rollback strategy, health
checks, and monitoring configuration.
Usage:
python deployment_planner.py --type webapp --environments dev,staging,prod
python deployment_planner.py --type microservice --environments dev,staging,prod --strategy canary
python deployment_planner.py --type library --environments staging,prod
python deployment_planner.py --type webapp --environments dev,staging,prod --format json
"""
import argparse
import json
import sys
import textwrap
from datetime import datetime
# ---------------------------------------------------------------------------
# Project type configurations
# ---------------------------------------------------------------------------
PROJECT_TYPES = {
"webapp": {
"label": "Web Application",
"description": "Full-stack web application with frontend and backend components.",
"default_strategy": "blue-green",
"recommended_strategies": ["blue-green", "canary", "rolling"],
"artifacts": ["Docker image", "Static assets (CDN)", "Database migrations"],
"health_checks": [
{"endpoint": "/health", "method": "GET", "expected_status": 200, "timeout_seconds": 5},
{"endpoint": "/api/health", "method": "GET", "expected_status": 200, "timeout_seconds": 10},
{"endpoint": "/", "method": "GET", "expected_status": 200, "timeout_seconds": 5},
],
"monitoring_metrics": [
"HTTP error rate (5xx)",
"P50/P95/P99 response latency",
"Request throughput (req/s)",
"CPU and memory utilization",
"Active database connections",
"Cache hit rate",
],
"rollback_steps": [
"Redirect traffic to previous deployment",
"Verify previous version health checks pass",
"Roll back database migrations if needed (backward-compatible only)",
"Invalidate CDN cache for reverted static assets",
"Notify on-call team via PagerDuty/Slack",
],
"pre_deploy_checks": [
"All CI checks pass (lint, test, build, security scan)",
"Database migration is backward-compatible",
"Feature flags configured for new features",
"Rollback procedure tested in staging",
"Monitoring dashboards reviewed",
],
"post_deploy_checks": [
"Health endpoints return 200",
"Error rate below threshold (< 0.1%)",
"Latency within SLA bounds",
"Key user journeys verified (smoke tests)",
"No increase in error log volume",
],
},
"microservice": {
"label": "Microservice",
"description": "Individual microservice deployed as a container, communicating via API or message queue.",
"default_strategy": "canary",
"recommended_strategies": ["canary", "rolling", "blue-green"],
"artifacts": ["Docker image", "API schema (OpenAPI)", "Service mesh config"],
"health_checks": [
{"endpoint": "/health", "method": "GET", "expected_status": 200, "timeout_seconds": 5},
{"endpoint": "/ready", "method": "GET", "expected_status": 200, "timeout_seconds": 5},
],
"monitoring_metrics": [
"gRPC/HTTP error rate",
"Request latency by endpoint",
"Message queue depth/lag",
"Circuit breaker state",
"Pod restart count",
"Memory and CPU per pod",
],
"rollback_steps": [
"Scale down new version pods",
"Scale up previous version pods",
"Verify service mesh routing restored",
"Check upstream/downstream service health",
"Review distributed traces for cascading failures",
],
"pre_deploy_checks": [
"Contract tests pass with upstream and downstream services",
"API schema backward-compatible (no breaking changes)",
"Resource limits configured (CPU, memory)",
"Horizontal pod autoscaler tested",
"Circuit breaker thresholds configured",
],
"post_deploy_checks": [
"Readiness probe passes",
"Liveness probe passes",
"No increase in upstream error rates",
"Message queue consumers healthy",
"Distributed traces show normal latency",
],
},
"library": {
"label": "Library / Package",
"description": "Shared library or package published to a registry (npm, PyPI, crates.io, etc.).",
"default_strategy": "rolling",
"recommended_strategies": ["rolling"],
"artifacts": ["Package archive", "Documentation site", "Changelog"],
"health_checks": [
{"endpoint": "Registry package page", "method": "GET", "expected_status": 200, "timeout_seconds": 10},
],
"monitoring_metrics": [
"Download count (post-release)",
"Issue/bug report rate",
"Dependency compatibility (CI matrix)",
"Documentation site uptime",
],
"rollback_steps": [
"Yank/unpublish the broken version from registry",
"Publish a patch version with the fix",
"Notify consumers via changelog and GitHub advisory",
"Update dependent projects pinned to the broken version",
],
"pre_deploy_checks": [
"All tests pass across supported platform matrix",
"Changelog updated with release notes",
"Version bumped according to semver",
"No breaking changes without major version bump",
"Documentation updated for new APIs",
],
"post_deploy_checks": [
"Package installable from registry",
"Basic usage example works with new version",
"Documentation site reflects new version",
"No compatibility issues reported in first 24 hours",
],
},
"mobile": {
"label": "Mobile Application",
"description": "iOS/Android mobile application distributed via app stores.",
"default_strategy": "canary",
"recommended_strategies": ["canary", "rolling"],
"artifacts": ["iOS .ipa / Android .aab", "App store metadata", "Release notes"],
"health_checks": [
{"endpoint": "App store listing", "method": "GET", "expected_status": 200, "timeout_seconds": 30},
{"endpoint": "/api/mobile/health", "method": "GET", "expected_status": 200, "timeout_seconds": 10},
],
"monitoring_metrics": [
"Crash-free session rate",
"App Not Responding (ANR) rate",
"API error rate from mobile clients",
"App launch time",
"User retention (day 1, day 7)",
"Store rating changes",
],
"rollback_steps": [
"Submit expedited review for hotfix build",
"Enable server-side kill switch for broken features",
"Roll back backend API to support previous app version",
"Communicate via in-app messaging about known issues",
],
"pre_deploy_checks": [
"UI tests pass on target device matrix",
"Backend APIs backward-compatible with previous app version",
"App size within acceptable limits",
"Staged rollout percentage configured (5% initially)",
"Crash reporting SDK configured",
],
"post_deploy_checks": [
"Crash-free rate above 99.5%",
"No spike in ANR reports",
"App store review approved",
"Staged rollout metrics healthy before increasing percentage",
],
},
"infrastructure": {
"label": "Infrastructure (IaC)",
"description": "Infrastructure as Code changes (Terraform, Pulumi, CloudFormation).",
"default_strategy": "rolling",
"recommended_strategies": ["rolling", "blue-green"],
"artifacts": ["Terraform plan", "State file backup", "Drift report"],
"health_checks": [
{"endpoint": "Cloud provider health API", "method": "GET", "expected_status": 200, "timeout_seconds": 15},
],
"monitoring_metrics": [
"Resource provisioning success rate",
"Infrastructure drift count",
"Cloud spend delta (pre/post deploy)",
"Service availability during change",
"DNS propagation status",
],
"rollback_steps": [
"Apply previous Terraform state (terraform apply -target)",
"Restore from state file backup",
"Verify all dependent services reconnect",
"Check DNS and load balancer routing",
"Review cloud audit logs for partial changes",
],
"pre_deploy_checks": [
"Terraform plan reviewed and approved",
"No destructive changes without explicit confirmation",
"State file backed up",
"Blast radius assessed (how many services affected)",
"Maintenance window scheduled if needed",
],
"post_deploy_checks": [
"All resources in desired state (no drift)",
"Dependent services healthy",
"Network connectivity verified",
"Cloud costs within expected range",
"Security group rules correct",
],
},
}
# ---------------------------------------------------------------------------
# Deployment strategy details
# ---------------------------------------------------------------------------
STRATEGIES = {
"blue-green": {
"label": "Blue-Green",
"description": "Maintain two identical environments. Deploy to the inactive one, then switch traffic.",
"pros": [
"Zero-downtime deployment",
"Instant rollback (switch back to old environment)",
"Full production testing before traffic switch",
],
"cons": [
"Requires 2x infrastructure during deployment",
"Database migrations need careful handling",
"Stateful services complicate the switch",
],
"phases": [
{"name": "Prepare green", "duration": "5-10 min", "action": "Deploy new version to inactive environment"},
{"name": "Validate green", "duration": "5-15 min", "action": "Run health checks and smoke tests on green"},
{"name": "Switch traffic", "duration": "< 1 min", "action": "Update load balancer to point to green"},
{"name": "Monitor", "duration": "15-30 min", "action": "Watch error rates and latency on green"},
{"name": "Decommission blue", "duration": "5 min", "action": "Tear down old environment (or keep as rollback)"},
],
"rollback_time": "< 1 minute (traffic switch)",
},
"canary": {
"label": "Canary",
"description": "Route a small percentage of traffic to the new version; increase gradually.",
"pros": [
"Low risk -- only a fraction of users see the new version initially",
"Real production traffic validates the release",
"Gradual rollout allows early detection of issues",
],
"cons": [
"Complex traffic routing configuration",
"Requires good observability to detect issues at low traffic percentages",
"Longer total deployment time",
],
"phases": [
{"name": "Deploy canary", "duration": "5 min", "action": "Deploy new version alongside stable"},
{"name": "Route 5%", "duration": "15 min", "action": "Send 5% traffic to canary, monitor"},
{"name": "Route 25%", "duration": "30 min", "action": "Increase to 25% if metrics healthy"},
{"name": "Route 50%", "duration": "60 min", "action": "Increase to 50% if metrics healthy"},
{"name": "Route 100%", "duration": "ongoing", "action": "Promote canary to stable, remove old version"},
],
"rollback_time": "< 1 minute (route 100% back to stable)",
},
"rolling": {
"label": "Rolling Update",
"description": "Replace instances one at a time (or in batches), maintaining availability throughout.",
"pros": [
"No additional infrastructure required",
"Built into Kubernetes and most orchestrators",
"Simple to configure and understand",
],
"cons": [
"Mixed versions running during deployment",
"Rollback requires another rolling update",
"Harder to test with full production traffic before commit",
],
"phases": [
{"name": "Start rollout", "duration": "1 min", "action": "Begin replacing instances (maxSurge/maxUnavailable)"},
{"name": "Rolling replace", "duration": "5-20 min", "action": "Instances replaced incrementally with health checks"},
{"name": "Verify", "duration": "5 min", "action": "Confirm all instances on new version and healthy"},
{"name": "Monitor", "duration": "15 min", "action": "Watch metrics for regression"},
],
"rollback_time": "5-20 minutes (rolling back to previous version)",
},
}
# ---------------------------------------------------------------------------
# Environment templates
# ---------------------------------------------------------------------------
ENVIRONMENT_DEFAULTS = {
"dev": {
"label": "Development",
"deploy_trigger": "Every push to feature branch",
"approval_required": False,
"replicas": 1,
"auto_scale": False,
"monitoring_level": "Basic (logs only)",
"alerting": False,
"data": "Seed/fixture data",
"secrets_source": "Repository secrets",
"retention_days": 7,
},
"staging": {
"label": "Staging",
"deploy_trigger": "Merge to main branch",
"approval_required": False,
"replicas": 2,
"auto_scale": False,
"monitoring_level": "Full observability",
"alerting": False,
"data": "Anonymized production clone",
"secrets_source": "Environment secrets",
"retention_days": 30,
},
"prod": {
"label": "Production",
"deploy_trigger": "Manual approval after staging validation",
"approval_required": True,
"replicas": 3,
"auto_scale": True,
"monitoring_level": "Full observability + tracing",
"alerting": True,
"data": "Production data",
"secrets_source": "Vault / OIDC",
"retention_days": 90,
},
"qa": {
"label": "QA / Testing",
"deploy_trigger": "On-demand or PR-based",
"approval_required": False,
"replicas": 1,
"auto_scale": False,
"monitoring_level": "Full observability",
"alerting": False,
"data": "Test data set",
"secrets_source": "Environment secrets",
"retention_days": 14,
},
"uat": {
"label": "User Acceptance Testing",
"deploy_trigger": "Manual promotion from staging",
"approval_required": True,
"replicas": 2,
"auto_scale": False,
"monitoring_level": "Full observability",
"alerting": False,
"data": "Anonymized production clone",
"secrets_source": "Environment secrets",
"retention_days": 30,
},
}
# ---------------------------------------------------------------------------
# Plan generation
# ---------------------------------------------------------------------------
def generate_plan(project_type_key, environment_names, strategy_key, output_format):
"""Generate a deployment plan."""
project_type = PROJECT_TYPES.get(project_type_key)
if not project_type:
return {"error": f"Unsupported project type: {project_type_key}. Supported: {', '.join(PROJECT_TYPES)}"}
strategy_key = strategy_key or project_type["default_strategy"]
strategy = STRATEGIES.get(strategy_key)
if not strategy:
return {"error": f"Unsupported strategy: {strategy_key}. Supported: {', '.join(STRATEGIES)}"}
env_names = [e.strip() for e in environment_names.split(",")]
environments = {}
for name in env_names:
if name in ENVIRONMENT_DEFAULTS:
environments[name] = ENVIRONMENT_DEFAULTS[name].copy()
else:
# Custom environment with sensible defaults
environments[name] = {
"label": name.capitalize(),
"deploy_trigger": "Manual",
"approval_required": False,
"replicas": 1,
"auto_scale": False,
"monitoring_level": "Basic",
"alerting": False,
"data": "Custom data",
"secrets_source": "Environment secrets",
"retention_days": 14,
}
plan = {
"project_type": project_type_key,
"project_label": project_type["label"],
"description": project_type["description"],
"strategy": strategy_key,
"strategy_label": strategy["label"],
"strategy_description": strategy["description"],
"environments": environments,
"artifacts": project_type["artifacts"],
"health_checks": project_type["health_checks"],
"monitoring_metrics": project_type["monitoring_metrics"],
"pre_deploy_checks": project_type["pre_deploy_checks"],
"post_deploy_checks": project_type["post_deploy_checks"],
"rollback_steps": project_type["rollback_steps"],
"strategy_details": {
"pros": strategy["pros"],
"cons": strategy["cons"],
"phases": strategy["phases"],
"rollback_time": strategy["rollback_time"],
},
"generated_at": datetime.utcnow().isoformat() + "Z",
}
if output_format == "json":
return plan
return _format_plan_markdown(plan)
def _format_plan_markdown(plan):
"""Format the deployment plan as a readable markdown document."""
lines = []
lines.append(f"# Deployment Plan: {plan['project_label']}")
lines.append("")
lines.append(f"**Generated:** {plan['generated_at']}")
lines.append(f"**Project Type:** {plan['project_label']}")
lines.append(f"**Strategy:** {plan['strategy_label']}")
lines.append(f"**Environments:** {', '.join(plan['environments'].keys())}")
lines.append("")
lines.append(f"> {plan['description']}")
lines.append("")
# Strategy overview
lines.append("## Deployment Strategy")
lines.append("")
lines.append(f"### {plan['strategy_label']}")
lines.append("")
lines.append(plan["strategy_description"])
lines.append("")
lines.append("**Advantages:**")
for pro in plan["strategy_details"]["pros"]:
lines.append(f"- {pro}")
lines.append("")
lines.append("**Trade-offs:**")
for con in plan["strategy_details"]["cons"]:
lines.append(f"- {con}")
lines.append("")
lines.append(f"**Estimated rollback time:** {plan['strategy_details']['rollback_time']}")
lines.append("")
lines.append("### Deployment Phases")
lines.append("")
lines.append("| Phase | Duration | Action |")
lines.append("|-------|----------|--------|")
for phase in plan["strategy_details"]["phases"]:
lines.append(f"| {phase['name']} | {phase['duration']} | {phase['action']} |")
lines.append("")
# Environment matrix
lines.append("## Environment Matrix")
lines.append("")
env_keys = list(plan["environments"].keys())
header_row = "| Aspect | " + " | ".join(plan["environments"][e]["label"] for e in env_keys) + " |"
separator = "|--------|" + "|".join("------" for _ in env_keys) + "|"
aspects = [
("Deploy trigger", "deploy_trigger"),
("Approval required", "approval_required"),
("Replicas", "replicas"),
("Auto-scale", "auto_scale"),
("Monitoring", "monitoring_level"),
("Alerting", "alerting"),
("Data source", "data"),
("Secrets source", "secrets_source"),
("Log retention", "retention_days"),
]
lines.append(header_row)
lines.append(separator)
for label, key in aspects:
values = []
for e in env_keys:
val = plan["environments"][e].get(key, "N/A")
if isinstance(val, bool):
val = "Yes" if val else "No"
elif key == "retention_days":
val = f"{val} days"
values.append(str(val))
lines.append(f"| {label} | " + " | ".join(values) + " |")
lines.append("")
# Artifacts
lines.append("## Build Artifacts")
lines.append("")
for artifact in plan["artifacts"]:
lines.append(f"- {artifact}")
lines.append("")
# Pre-deploy checks
lines.append("## Pre-Deployment Checklist")
lines.append("")
for i, check in enumerate(plan["pre_deploy_checks"], 1):
lines.append(f"- [ ] {check}")
lines.append("")
# Health checks
lines.append("## Health Checks")
lines.append("")
lines.append("| Endpoint | Method | Expected Status | Timeout |")
lines.append("|----------|--------|----------------|---------|")
for hc in plan["health_checks"]:
lines.append(f"| `{hc['endpoint']}` | {hc['method']} | {hc['expected_status']} | {hc['timeout_seconds']}s |")
lines.append("")
lines.append("**Health check verification script:**")
lines.append("")
lines.append("```bash")
lines.append("#!/bin/bash")
lines.append('HEALTH_URL="${1:?Usage: health-check.sh <base-url>}"')
lines.append("")
for hc in plan["health_checks"]:
lines.append(f'echo "Checking {hc["endpoint"]}..."')
lines.append(f'STATUS=$(curl -s -o /dev/null -w "%{{http_code}}" --max-time {hc["timeout_seconds"]} "$HEALTH_URL{hc["endpoint"]}")')
lines.append(f'if [ "$STATUS" -ne {hc["expected_status"]} ]; then')
lines.append(f' echo "FAIL: {hc["endpoint"]} returned $STATUS (expected {hc["expected_status"]})"')
lines.append(f' exit 1')
lines.append(f'fi')
lines.append(f'echo "OK: {hc["endpoint"]} returned $STATUS"')
lines.append("")
lines.append('echo "All health checks passed."')
lines.append("```")
lines.append("")
# Post-deploy checks
lines.append("## Post-Deployment Verification")
lines.append("")
for check in plan["post_deploy_checks"]:
lines.append(f"- [ ] {check}")
lines.append("")
# Monitoring
lines.append("## Monitoring Metrics")
lines.append("")
lines.append("Track these metrics before, during, and after deployment:")
lines.append("")
for metric in plan["monitoring_metrics"]:
lines.append(f"- {metric}")
lines.append("")
# Rollback
lines.append("## Rollback Procedure")
lines.append("")
lines.append(f"**Estimated rollback time:** {plan['strategy_details']['rollback_time']}")
lines.append("")
lines.append("**Steps:**")
lines.append("")
for i, step in enumerate(plan["rollback_steps"], 1):
lines.append(f"{i}. {step}")
lines.append("")
lines.append("**Rollback triggers (auto-rollback if any are met):**")
lines.append("")
lines.append("- Error rate exceeds 1% for 2+ minutes")
lines.append("- P99 latency exceeds 2x baseline for 5+ minutes")
lines.append("- Health check failures on 2+ consecutive checks")
lines.append("- Critical alert fires within 15 minutes of deployment")
lines.append("")
# Promotion flow
lines.append("## Environment Promotion Flow")
lines.append("")
env_labels = [plan["environments"][e]["label"] for e in env_keys]
flow_parts = []
for i, label in enumerate(env_labels):
if plan["environments"][env_keys[i]].get("approval_required"):
flow_parts.append(f"[Approval Gate] -> {label}")
else:
flow_parts.append(label)
lines.append("```")
lines.append(" -> ".join(flow_parts))
lines.append("```")
lines.append("")
return "\n".join(lines)
# ---------------------------------------------------------------------------
# CLI
# ---------------------------------------------------------------------------
def main():
parser = argparse.ArgumentParser(
description="Generate a deployment plan based on project type and target environments.",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog=textwrap.dedent("""\
Project types: webapp, microservice, library, mobile, infrastructure
Strategies: blue-green, canary, rolling
Environment names: dev, staging, prod, qa, uat (or any custom name)
Examples:
%(prog)s --type webapp --environments dev,staging,prod
%(prog)s --type microservice --environments dev,staging,prod --strategy canary
%(prog)s --type library --environments staging,prod
%(prog)s --type mobile --environments dev,qa,staging,prod --strategy canary
%(prog)s --type infrastructure --environments staging,prod --strategy rolling
%(prog)s --type webapp --environments dev,staging,prod --format json
"""),
)
parser.add_argument(
"--type",
required=True,
choices=list(PROJECT_TYPES.keys()),
help="Project type.",
)
parser.add_argument(
"--environments",
required=True,
help="Comma-separated list of environment names (e.g., dev,staging,prod).",
)
parser.add_argument(
"--strategy",
choices=list(STRATEGIES.keys()),
help="Deployment strategy (default depends on project type).",
)
parser.add_argument(
"--format",
choices=["text", "json"],
default="text",
help="Output format (default: text/markdown).",
)
parser.add_argument(
"--output", "-o",
help="Write plan to file instead of stdout.",
)
args = parser.parse_args()
result = generate_plan(args.type, args.environments, args.strategy, args.format)
# Handle errors
if isinstance(result, dict) and "error" in result:
print(f"Error: {result['error']}", file=sys.stderr)
sys.exit(1)
# Output
if isinstance(result, dict):
output_text = json.dumps(result, indent=2)
else:
output_text = result
if args.output:
with open(args.output, "w") as f:
f.write(output_text)
print(f"Deployment plan written to {args.output}", file=sys.stderr)
else:
print(output_text)
if __name__ == "__main__":
main()