Mastering GitHub Actions: Advanced Conditional Workflows with Dynamic Inputs and Job Orchestration
Mastering GitHub Actions: Advanced Conditional Workflows with Dynamic Inputs and Job Orchestration
GitHub Actions workflows become incredibly powerful when you can control their execution based on user inputs and dynamic conditions. This comprehensive guide explores advanced patterns for implementing conditional workflows, from basic input-driven job selection to sophisticated orchestration strategies that adapt to different deployment scenarios.
Understanding workflow_dispatch and Input-Driven Automation
The workflow_dispatch trigger enables manual workflow execution with customizable inputs, providing the foundation for building flexible, user-controlled automation pipelines.
Basic Input Types and Configuration
GitHub Actions supports several input types that enable rich user interaction:
name: Advanced Conditional Deployment
on:
workflow_dispatch:
inputs:
environment:
description: 'Target deployment environment'
type: choice
options:
- development
- staging
- production
required: true
default: 'development'
service_name:
description: 'Service to deploy'
type: choice
options:
- user-service
- payment-service
- notification-service
- all-services
required: true
deployment_strategy:
description: 'Deployment strategy'
type: choice
options:
- rolling-update
- blue-green
- canary
required: true
default: 'rolling-update'
skip_tests:
description: 'Skip test execution'
type: boolean
default: false
custom_tag:
description: 'Custom Docker tag (optional)'
type: string
required: false
rollback_version:
description: 'Version to rollback to (for rollback deployments)'
type: string
required: false
env:
DEPLOYMENT_ENV: ${{ github.event.inputs.environment }}
SERVICE_NAME: ${{ github.event.inputs.service_name }}
STRATEGY: ${{ github.event.inputs.deployment_strategy }}
Advanced Input Validation and Processing
jobs:
validate-inputs:
runs-on: ubuntu-latest
outputs:
is-valid: ${{ steps.validation.outputs.valid }}
docker-tag: ${{ steps.tag-generation.outputs.tag }}
services-to-deploy: ${{ steps.service-parsing.outputs.services }}
steps:
- name: Validate Environment Input
id: validation
run: |
if [[ "${{ github.event.inputs.environment }}" == "production" ]]; then
if [[ "${{ github.actor }}" != "admin-user" ]] && [[ "${{ github.actor }}" != "devops-team" ]]; then
echo "โ Production deployments require admin privileges"
echo "valid=false" >> $GITHUB_OUTPUT
exit 1
fi
fi
if [[ "${{ github.event.inputs.deployment_strategy }}" == "blue-green" ]] && [[ "${{ github.event.inputs.environment }}" != "production" ]]; then
echo "โ ๏ธ Blue-green deployment is only available for production"
echo "valid=false" >> $GITHUB_OUTPUT
exit 1
fi
echo "โ
Input validation passed"
echo "valid=true" >> $GITHUB_OUTPUT
- name: Generate Docker Tag
id: tag-generation
run: |
if [[ -n "${{ github.event.inputs.custom_tag }}" ]]; then
TAG="${{ github.event.inputs.custom_tag }}"
else
TAG="${{ github.sha }}"
fi
echo "tag=${TAG}" >> $GITHUB_OUTPUT
echo "๐ท๏ธ Using Docker tag: ${TAG}"
- name: Parse Services to Deploy
id: service-parsing
run: |
if [[ "${{ github.event.inputs.service_name }}" == "all-services" ]]; then
SERVICES="user-service,payment-service,notification-service"
else
SERVICES="${{ github.event.inputs.service_name }}"
fi
echo "services=${SERVICES}" >> $GITHUB_OUTPUT
echo "๐ฏ Services to deploy: ${SERVICES}"
Conditional Job Execution Patterns
Pattern 1: Environment-Based Job Execution
# Development Environment Jobs
deploy-to-dev:
runs-on: ubuntu-latest
if: ${{ github.event.inputs.environment == 'development' && needs.validate-inputs.outputs.is-valid == 'true' }}
needs: validate-inputs
environment: development
steps:
- name: Checkout Code
uses: actions/checkout@v4
- name: Deploy to Development
run: |
echo "๐ Deploying ${{ needs.validate-inputs.outputs.services-to-deploy }} to development"
# Fast deployment without extensive testing
./scripts/deploy-dev.sh \
--services="${{ needs.validate-inputs.outputs.services-to-deploy }}" \
--tag="${{ needs.validate-inputs.outputs.docker-tag }}"
# Staging Environment Jobs
deploy-to-staging:
runs-on: ubuntu-latest
if: ${{ github.event.inputs.environment == 'staging' && needs.validate-inputs.outputs.is-valid == 'true' }}
needs: [validate-inputs, run-tests]
environment: staging
steps:
- name: Checkout Code
uses: actions/checkout@v4
- name: Deploy to Staging
run: |
echo "๐ญ Deploying ${{ needs.validate-inputs.outputs.services-to-deploy }} to staging"
./scripts/deploy-staging.sh \
--services="${{ needs.validate-inputs.outputs.services-to-deploy }}" \
--tag="${{ needs.validate-inputs.outputs.docker-tag }}" \
--strategy="${{ github.event.inputs.deployment_strategy }}"
# Production Environment Jobs (with approval)
deploy-to-production:
runs-on: ubuntu-latest
if: ${{ github.event.inputs.environment == 'production' && needs.validate-inputs.outputs.is-valid == 'true' }}
needs: [validate-inputs, run-tests, security-scan]
environment:
name: production
url: https://app.production.com
steps:
- name: Checkout Code
uses: actions/checkout@v4
- name: Production Deployment
run: |
echo "๐ญ Deploying ${{ needs.validate-inputs.outputs.services-to-deploy }} to production"
./scripts/deploy-production.sh \
--services="${{ needs.validate-inputs.outputs.services-to-deploy }}" \
--tag="${{ needs.validate-inputs.outputs.docker-tag }}" \
--strategy="${{ github.event.inputs.deployment_strategy }}"
Pattern 2: Service-Specific Job Execution
# User Service Deployment
deploy-user-service:
runs-on: ubuntu-latest
if: ${{ contains(needs.validate-inputs.outputs.services-to-deploy, 'user-service') }}
needs: validate-inputs
steps:
- name: Deploy User Service
run: |
echo "๐ค Deploying User Service"
./scripts/deploy-service.sh \
--service="user-service" \
--environment="${{ github.event.inputs.environment }}" \
--tag="${{ needs.validate-inputs.outputs.docker-tag }}"
# Service-specific health checks
curl -f "https://user-service.${{ github.event.inputs.environment }}.com/health" || exit 1
# Payment Service Deployment
deploy-payment-service:
runs-on: ubuntu-latest
if: ${{ contains(needs.validate-inputs.outputs.services-to-deploy, 'payment-service') }}
needs: [validate-inputs, security-scan] # Payment service requires security scan
steps:
- name: Deploy Payment Service
run: |
echo "๐ณ Deploying Payment Service"
./scripts/deploy-service.sh \
--service="payment-service" \
--environment="${{ github.event.inputs.environment }}" \
--tag="${{ needs.validate-inputs.outputs.docker-tag }}"
# PCI compliance checks for payment service
./scripts/pci-compliance-check.sh
Pattern 3: Strategy-Based Deployment Jobs
# Rolling Update Deployment
rolling-deployment:
runs-on: ubuntu-latest
if: ${{ github.event.inputs.deployment_strategy == 'rolling-update' }}
needs: validate-inputs
steps:
- name: Rolling Update Deployment
run: |
echo "๐ Executing rolling update deployment"
kubectl set image deployment/app \
app="${{ needs.validate-inputs.outputs.docker-tag }}" \
--record
kubectl rollout status deployment/app
# Blue-Green Deployment
blue-green-deployment:
runs-on: ubuntu-latest
if: ${{ github.event.inputs.deployment_strategy == 'blue-green' }}
needs: validate-inputs
steps:
- name: Blue-Green Deployment
run: |
echo "๐ต๐ข Executing blue-green deployment"
# Deploy to green environment
./scripts/deploy-green.sh --tag="${{ needs.validate-inputs.outputs.docker-tag }}"
# Health check green environment
./scripts/health-check-green.sh
# Switch traffic to green
./scripts/switch-to-green.sh
# Cleanup blue environment
./scripts/cleanup-blue.sh
# Canary Deployment
canary-deployment:
runs-on: ubuntu-latest
if: ${{ github.event.inputs.deployment_strategy == 'canary' }}
needs: validate-inputs
steps:
- name: Canary Deployment
run: |
echo "๐ค Executing canary deployment"
# Deploy canary version (10% traffic)
./scripts/deploy-canary.sh \
--tag="${{ needs.validate-inputs.outputs.docker-tag }}" \
--traffic-percentage="10"
# Monitor canary metrics
sleep 300 # 5 minutes monitoring
./scripts/check-canary-metrics.sh
# Promote to full deployment if metrics are good
./scripts/promote-canary.sh
Advanced Dynamic Job Generation
Matrix Strategy with Input-Based Configuration
dynamic-service-deployment:
runs-on: ubuntu-latest
if: ${{ needs.validate-inputs.outputs.is-valid == 'true' }}
needs: validate-inputs
strategy:
matrix:
service: ${{ fromJson(needs.parse-services.outputs.service-matrix) }}
environment: [${{ github.event.inputs.environment }}]
fail-fast: false
max-parallel: 3
steps:
- name: Deploy Service
run: |
echo "๐ Deploying ${{ matrix.service }} to ${{ matrix.environment }}"
./scripts/deploy-service.sh \
--service="${{ matrix.service }}" \
--environment="${{ matrix.environment }}" \
--tag="${{ needs.validate-inputs.outputs.docker-tag }}"
parse-services:
runs-on: ubuntu-latest
needs: validate-inputs
outputs:
service-matrix: ${{ steps.create-matrix.outputs.matrix }}
steps:
- name: Create Service Matrix
id: create-matrix
run: |
SERVICES="${{ needs.validate-inputs.outputs.services-to-deploy }}"
# Convert comma-separated services to JSON array
SERVICE_ARRAY=$(echo $SERVICES | jq -R 'split(",")')
echo "matrix=${SERVICE_ARRAY}" >> $GITHUB_OUTPUT
Conditional Test Execution
run-tests:
runs-on: ubuntu-latest
if: ${{ github.event.inputs.skip_tests == 'false' && needs.validate-inputs.outputs.is-valid == 'true' }}
needs: validate-inputs
strategy:
matrix:
test-type: [unit, integration, e2e]
exclude:
# Skip e2e tests for development environment
- test-type: e2e
environment: ${{ github.event.inputs.environment == 'development' && 'development' || '' }}
steps:
- name: Checkout Code
uses: actions/checkout@v4
- name: Run Tests
run: |
case "${{ matrix.test-type }}" in
"unit")
echo "๐งช Running unit tests"
npm run test:unit
;;
"integration")
echo "๐ Running integration tests"
npm run test:integration
;;
"e2e")
echo "๐ญ Running e2e tests"
npm run test:e2e
;;
esac
- name: Upload Test Results
uses: actions/upload-artifact@v4
if: always()
with:
name: test-results-${{ matrix.test-type }}
path: test-results/
Security and Approval Workflows
Environment Protection Rules
security-scan:
runs-on: ubuntu-latest
if: ${{ github.event.inputs.environment == 'production' || github.event.inputs.environment == 'staging' }}
needs: validate-inputs
steps:
- name: Checkout Code
uses: actions/checkout@v4
- name: Container Security Scan
uses: aquasecurity/trivy-action@master
with:
image-ref: '${{ needs.validate-inputs.outputs.docker-tag }}'
format: 'sarif'
output: 'trivy-results.sarif'
- name: Upload Trivy Scan Results
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: 'trivy-results.sarif'
- name: SAST Security Scan
uses: securecodewarrior/github-action-add-sarif@v1
with:
sarif-file: security-scan-results.sarif
compliance-check:
runs-on: ubuntu-latest
if: ${{ github.event.inputs.environment == 'production' }}
needs: [validate-inputs, security-scan]
steps:
- name: SOC2 Compliance Check
run: |
echo "๐ Running SOC2 compliance checks"
./scripts/soc2-compliance.sh
- name: PCI DSS Validation
if: ${{ contains(needs.validate-inputs.outputs.services-to-deploy, 'payment-service') }}
run: |
echo "๐ณ Running PCI DSS validation"
./scripts/pci-dss-check.sh
Manual Approval Integration
request-approval:
runs-on: ubuntu-latest
if: ${{ github.event.inputs.environment == 'production' }}
needs: [validate-inputs, run-tests, security-scan]
environment:
name: production-approval
steps:
- name: Request Deployment Approval
uses: trstringer/manual-approval@v1
with:
secret: ${{ github.TOKEN }}
approvers: devops-team,security-team
minimum-approvals: 2
issue-title: "Production Deployment Approval"
issue-body: |
## Production Deployment Request
**Services:** ${{ needs.validate-inputs.outputs.services-to-deploy }}
**Tag:** ${{ needs.validate-inputs.outputs.docker-tag }}
**Strategy:** ${{ github.event.inputs.deployment_strategy }}
**Requested by:** ${{ github.actor }}
### Pre-deployment Checklist
- [ ] Security scans passed
- [ ] Tests completed successfully
- [ ] Rollback plan documented
- [ ] Monitoring alerts configured
Please review and approve this deployment.
Rollback and Recovery Workflows
Automated Rollback on Failure
monitor-deployment:
runs-on: ubuntu-latest
if: ${{ always() && (needs.deploy-to-production.result == 'success' || needs.deploy-to-staging.result == 'success') }}
needs: [deploy-to-production, deploy-to-staging]
steps:
- name: Monitor Deployment Health
id: health-check
run: |
echo "๐ Monitoring deployment health for 5 minutes"
for i in {1..10}; do
if curl -f "https://app.${{ github.event.inputs.environment }}.com/health"; then
echo "โ
Health check $i passed"
else
echo "โ Health check $i failed"
echo "health-failed=true" >> $GITHUB_OUTPUT
break
fi
sleep 30
done
- name: Trigger Rollback
if: ${{ steps.health-check.outputs.health-failed == 'true' }}
uses: actions/github-script@v7
with:
script: |
const { data: workflow } = await github.rest.actions.createWorkflowDispatch({
owner: context.repo.owner,
repo: context.repo.repo,
workflow_id: 'rollback.yml',
ref: 'main',
inputs: {
environment: '${{ github.event.inputs.environment }}',
rollback_version: '${{ github.event.inputs.rollback_version }}',
reason: 'Automated rollback due to health check failure'
}
});
manual-rollback:
runs-on: ubuntu-latest
if: ${{ github.event.inputs.rollback_version != '' }}
needs: validate-inputs
steps:
- name: Execute Rollback
run: |
echo "โช Rolling back to version ${{ github.event.inputs.rollback_version }}"
./scripts/rollback.sh \
--version="${{ github.event.inputs.rollback_version }}" \
--environment="${{ github.event.inputs.environment }}" \
--services="${{ needs.validate-inputs.outputs.services-to-deploy }}"
Advanced Notification and Reporting
Comprehensive Deployment Notifications
notify-deployment-status:
runs-on: ubuntu-latest
if: always()
needs: [validate-inputs, deploy-to-production, deploy-to-staging, deploy-to-dev]
steps:
- name: Determine Deployment Status
id: status
run: |
if [[ "${{ needs.deploy-to-production.result }}" == "success" ]] || \
[[ "${{ needs.deploy-to-staging.result }}" == "success" ]] || \
[[ "${{ needs.deploy-to-dev.result }}" == "success" ]]; then
echo "status=success" >> $GITHUB_OUTPUT
echo "color=good" >> $GITHUB_OUTPUT
else
echo "status=failure" >> $GITHUB_OUTPUT
echo "color=danger" >> $GITHUB_OUTPUT
fi
- name: Slack Notification
uses: 8398a7/action-slack@v3
with:
status: ${{ steps.status.outputs.status }}
channel: '#deployments'
text: |
*Deployment ${{ steps.status.outputs.status }}*
โข **Environment:** ${{ github.event.inputs.environment }}
โข **Services:** ${{ needs.validate-inputs.outputs.services-to-deploy }}
โข **Strategy:** ${{ github.event.inputs.deployment_strategy }}
โข **Tag:** ${{ needs.validate-inputs.outputs.docker-tag }}
โข **Triggered by:** ${{ github.actor }}
<${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|View Workflow>
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }}
- name: Teams Notification
if: ${{ github.event.inputs.environment == 'production' }}
uses: skitionek/notify-microsoft-teams@master
with:
webhook_url: ${{ secrets.TEAMS_WEBHOOK }}
overwrite: |
{
"text": "Production Deployment ${{ steps.status.outputs.status }}",
"sections": [
{
"activityTitle": "Deployment Summary",
"facts": [
{"name": "Environment", "value": "${{ github.event.inputs.environment }}"},
{"name": "Services", "value": "${{ needs.validate-inputs.outputs.services-to-deploy }}"},
{"name": "Status", "value": "${{ steps.status.outputs.status }}"}
]
}
]
}
Cost Optimization and Resource Management
Dynamic Resource Allocation
optimize-resources:
runs-on: ubuntu-latest
if: ${{ github.event.inputs.environment == 'development' }}
needs: validate-inputs
steps:
- name: Scale Down Development Resources
run: |
echo "๐ฐ Optimizing development environment resources"
kubectl scale deployment --replicas=1 --all -n development
kubectl patch hpa --patch '{"spec":{"maxReplicas":3}}' --all -n development
weekend-shutdown:
runs-on: ubuntu-latest
if: ${{ github.event.inputs.environment == 'development' && github.event.schedule == '0 18 * * 5' }}
steps:
- name: Weekend Shutdown
run: |
echo "๐ Shutting down development environment for weekend"
kubectl scale deployment --replicas=0 --all -n development
Testing and Validation Strategies
Comprehensive Testing Pipeline
integration-tests:
runs-on: ubuntu-latest
if: ${{ github.event.inputs.skip_tests == 'false' }}
needs: [validate-inputs]
services:
postgres:
image: postgres:14
env:
POSTGRES_PASSWORD: testpass
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
redis:
image: redis:7
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- name: Checkout Code
uses: actions/checkout@v4
- name: Run Integration Tests
run: |
export DATABASE_URL="postgresql://postgres:testpass@localhost:5432/testdb"
export REDIS_URL="redis://localhost:6379"
npm run test:integration
- name: Load Test
if: ${{ github.event.inputs.environment != 'development' }}
run: |
echo "๐ Running load tests"
artillery quick \
--count 50 \
--num 100 \
https://app.${{ github.event.inputs.environment }}.com
Monitoring and Observability Integration
Deployment Tracking and Metrics
track-deployment:
runs-on: ubuntu-latest
needs: [deploy-to-production, deploy-to-staging, deploy-to-dev]
if: always()
steps:
- name: Record Deployment Metrics
run: |
# Send deployment event to DataDog
curl -X POST "https://api.datadoghq.com/api/v1/events" \
-H "Content-Type: application/json" \
-H "DD-API-KEY: ${{ secrets.DATADOG_API_KEY }}" \
-d '{
"title": "Deployment: ${{ github.event.inputs.service_name }}",
"text": "Deployed ${{ needs.validate-inputs.outputs.services-to-deploy }} to ${{ github.event.inputs.environment }}",
"tags": [
"environment:${{ github.event.inputs.environment }}",
"service:${{ github.event.inputs.service_name }}",
"strategy:${{ github.event.inputs.deployment_strategy }}"
]
}'
- name: Update Grafana Annotations
run: |
curl -X POST "https://grafana.company.com/api/annotations" \
-H "Authorization: Bearer ${{ secrets.GRAFANA_TOKEN }}" \
-H "Content-Type: application/json" \
-d '{
"text": "Deployment: ${{ github.event.inputs.service_name }}",
"tags": ["deployment", "${{ github.event.inputs.environment }}"],
"time": '${{ github.event.timestamp }}'
}'
Best Practices and Optimization
Workflow Performance Optimization
cache-dependencies:
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v4
- name: Cache Node Modules
uses: actions/cache@v3
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
- name: Cache Docker Layers
uses: actions/cache@v3
with:
path: /tmp/.buildx-cache
key: ${{ runner.os }}-buildx-${{ github.sha }}
restore-keys: |
${{ runner.os }}-buildx-
Security Best Practices
security-hardening:
runs-on: ubuntu-latest
steps:
- name: Harden Runner
uses: step-security/harden-runner@v2
with:
egress-policy: strict
allowed-endpoints: >
api.github.com:443
github.com:443
objects.githubusercontent.com:443
registry-1.docker.io:443
- name: Verify Signatures
run: |
# Verify container image signatures
cosign verify --key cosign.pub ${{ needs.validate-inputs.outputs.docker-tag }}
Conclusion
Advanced conditional workflows in GitHub Actions enable sophisticated automation strategies that adapt to different deployment scenarios, user requirements, and organizational policies. By leveraging input-driven job execution, dynamic matrix strategies, and comprehensive validation patterns, you can create robust CI/CD pipelines that provide flexibility without sacrificing security or reliability.
Key Takeaways
- Input Validation: Always validate user inputs before executing critical operations
- Conditional Logic: Use conditional expressions to control job execution based on inputs and environment state
- Security Integration: Implement security scans, compliance checks, and approval workflows for production deployments
- Monitoring: Include comprehensive monitoring, notification, and rollback capabilities
- Performance: Optimize workflows with caching, parallel execution, and resource management
- Documentation: Provide clear input descriptions and maintain workflow documentation
Implementation Strategy
Start Simple: Begin with basic conditional jobs and gradually add complexity Test Thoroughly: Validate workflows in development environments before production use Monitor Performance: Track workflow execution times and optimize bottlenecks Maintain Security: Regular security reviews and updates to approval processes Document Patterns: Create reusable templates and clear documentation for team adoption
By implementing these advanced patterns, you can create GitHub Actions workflows that provide the flexibility and control needed for modern DevOps practices while maintaining the reliability and security required for production environments.