Multi-Step Tasks
Multi-step tasks enable you to create complex workflows by combining multiple commands, tasks, and scripts into a single, orchestrated operation. They provide advanced control flow, error handling, and parallel execution capabilities.
Basic Multi-Step Structureβ
tasks:
deploy:
description: Deploy application
steps:
- command: git pull origin main
- command: npm install
- command: npm run build
- command: pm2 restart app
Step Typesβ
Command Stepsβ
Execute shell commands:
tasks:
setup:
steps:
- command: apt-get update
- command: apt-get install -y nginx
- command: systemctl start nginx
Task Stepsβ
Call other tasks:
tasks:
backup-db:
command: pg_dump production > backup.sql
backup-files:
command: tar -czf files.tar.gz /var/www
full-backup:
steps:
- task: backup-db
- task: backup-files
- command: rsync -av /backup/ remote:/backup/
Script Stepsβ
Execute JavaScript code:
tasks:
process:
steps:
- script: |
const data = await fetchData();
console.log(`Processing ${data.length} items`);
return data;
register: fetched_data
- script: |
const processed = fetched_data.map(transform);
await saveResults(processed);
Step Propertiesβ
Named Stepsβ
Add names for clarity:
tasks:
deploy:
steps:
- name: Pull latest code
command: git pull origin main
- name: Install dependencies
command: npm install
- name: Run tests
command: npm test
- name: Build application
command: npm run build
- name: Deploy to production
command: ./deploy.sh
Step Targetsβ
Override task target per step:
tasks:
multi-server:
steps:
- name: Update web server
command: apt-get update
target: hosts.web-server
- name: Update database server
command: apt-get update
target: hosts.db-server
- name: Update cache server
command: apt-get update
target: hosts.cache-server
Step Environmentβ
Set environment per step:
tasks:
build-multiple:
steps:
- name: Build for development
command: npm run build
env:
NODE_ENV: development
- name: Build for production
command: npm run build
env:
NODE_ENV: production
MINIFY: true
Parallel Executionβ
Parallel Stepsβ
Execute steps concurrently:
tasks:
parallel-deploy:
steps:
- name: Deploy services
parallel: true
steps:
- command: deploy-web.sh
target: hosts.web
- command: deploy-api.sh
target: hosts.api
- command: deploy-worker.sh
target: hosts.worker
Parallel with Groupsβ
Group parallel operations:
tasks:
complex-parallel:
steps:
- name: Prepare all servers
parallel: true
steps:
- command: prepare.sh
targets: ["hosts.web-1", "hosts.web-2"]
- name: Deploy application
parallel: true
steps:
- command: deploy.sh
targets: ["hosts.web-1", "hosts.web-2"]
- name: Verify deployment
command: health-check.sh
targets: ["hosts.web-1", "hosts.web-2"]
Conditional Executionβ
When Conditionsβ
Execute steps conditionally:
tasks:
smart-deploy:
steps:
- name: Check environment
command: echo $ENVIRONMENT
register: env_check
- name: Deploy to staging
command: deploy-staging.sh
when: env_check.stdout == "staging"
- name: Deploy to production
command: deploy-production.sh
when: env_check.stdout == "production"
- name: Validate deployment
command: validate.sh
when: env_check.exitCode == 0
Complex Conditionsβ
tasks:
conditional:
steps:
- command: check-health
register: health
- command: restart-service
when: health.exitCode != 0
- command: scale-up
when: |
health.stdout.includes("high-load") &&
params.autoScale == true
Error Handlingβ
Step Failure Behaviorβ
tasks:
resilient:
steps:
- name: Critical step
command: important-operation
onFailure: abort # Stop task (default)
- name: Optional step
command: nice-to-have
onFailure: continue # Continue to next step
- name: Ignorable step
command: cleanup-maybe
onFailure: ignore # Don't count as failure
Retry Logicβ
tasks:
with-retry:
steps:
- name: Connect to service
command: test-connection
onFailure:
retry: 3
delay: 5s
- name: Complex retry
command: unstable-operation
onFailure:
retry: 5
delay: 10s
backoff: exponential # 10s, 20s, 40s...
Error Recoveryβ
tasks:
with-recovery:
steps:
- name: Main operation
command: risky-operation
onFailure:
task: recovery-task
- name: Alternative approach
command: backup-operation
onFailure:
command: emergency-cleanup
Data Flowβ
Register Variablesβ
Pass data between steps:
tasks:
data-pipeline:
steps:
- name: Get version
command: cat version.txt
register: version
- name: Build with version
command: docker build -t app:${version.stdout} .
- name: Tag latest
command: docker tag app:${version.stdout} app:latest
when: version.stdout.includes("stable")
Complex Data Flowβ
tasks:
process-data:
steps:
- name: Fetch data
script: |
const data = await api.getData();
return { count: data.length, items: data };
register: dataset
- name: Process items
script: |
const processed = dataset.items.map(item => ({
...item,
processed: true
}));
return processed;
register: processed_data
- name: Save results
command: echo '${processed_data}' > results.json
Step Dependenciesβ
Sequential Dependenciesβ
tasks:
sequential:
steps:
- name: step1
command: echo "First"
id: first
- name: step2
command: echo "Second"
dependsOn: [first]
- name: step3
command: echo "Third"
dependsOn: [step2]
Complex Dependenciesβ
tasks:
complex-deps:
steps:
- name: prepare-db
command: setup-database.sh
id: db
- name: prepare-cache
command: setup-cache.sh
id: cache
- name: prepare-storage
command: setup-storage.sh
id: storage
- name: deploy-app
command: deploy.sh
dependsOn: [db, cache, storage]
Always Run Stepsβ
Execute regardless of previous failures:
tasks:
with-cleanup:
steps:
- name: Main operation
command: process-data
- name: Risky operation
command: dangerous-task
- name: Cleanup
command: cleanup-resources
alwaysRun: true # Runs even if previous steps failed
- name: Send notification
command: notify-complete
alwaysRun: true
Hooksβ
Task-Level Hooksβ
tasks:
monitored:
hooks:
before:
- command: echo "Task starting at $(date)"
- command: check-prerequisites
after:
- command: echo "Task completed at $(date)"
- command: cleanup-temp
onError:
- command: send-alert
- command: rollback
steps:
- command: main-operation
- command: verify-results
Step-Level Hooksβ
tasks:
detailed-monitoring:
steps:
- name: Critical operation
command: important-task
hooks:
before:
- command: create-snapshot
after:
- command: verify-state
onError:
- command: restore-snapshot
Real-World Examplesβ
CI/CD Pipelineβ
tasks:
ci-pipeline:
description: Complete CI/CD pipeline
steps:
- name: Checkout code
command: git checkout ${params.branch}
- name: Install dependencies
command: npm ci
timeout: 300000
- name: Run linting
command: npm run lint
onFailure: continue
- name: Run tests
command: npm test
onFailure: abort
- name: Build application
command: npm run build
env:
NODE_ENV: production
- name: Build Docker image
command: |
docker build -t app:${params.version} .
docker tag app:${params.version} app:latest
- name: Push to registry
command: docker push app:${params.version}
when: params.branch == "main"
- name: Deploy to staging
task: deploy-staging
when: params.branch == "develop"
- name: Deploy to production
task: deploy-production
when: params.branch == "main" && params.deploy == true
Database Migrationβ
tasks:
db-migration:
description: Safe database migration with rollback
steps:
- name: Create backup
command: pg_dump production > backup-$(date +%Y%m%d).sql
target: hosts.db-primary
register: backup_file
- name: Verify backup
command: test -s ${backup_file.stdout}
onFailure: abort
- name: Run migration
command: psql production < migration.sql
target: hosts.db-primary
register: migration_result
onFailure:
command: psql production < ${backup_file.stdout}
- name: Verify migration
script: |
const result = await db.query('SELECT version FROM schema_info');
if (result.version !== expectedVersion) {
throw new Error('Migration verification failed');
}
onFailure:
command: psql production < ${backup_file.stdout}
- name: Update replicas
parallel: true
steps:
- command: pg_dump production | psql replica
target: hosts.db-replica-1
- command: pg_dump production | psql replica
target: hosts.db-replica-2
- name: Cleanup old backup
command: find /backup -name "*.sql" -mtime +30 -delete
alwaysRun: true
Blue-Green Deploymentβ
tasks:
blue-green-deploy:
description: Zero-downtime blue-green deployment
steps:
- name: Identify current environment
command: kubectl get service app -o jsonpath='{.spec.selector.version}'
register: current_env
- name: Set target environment
script: |
const target = current_env.stdout === 'blue' ? 'green' : 'blue';
return { target };
register: deploy_env
- name: Deploy to inactive environment
command: |
kubectl set image deployment/app-${deploy_env.target} \
app=myapp:${params.version}
- name: Wait for rollout
command: |
kubectl rollout status deployment/app-${deploy_env.target} \
--timeout=600s
onFailure:
retry: 3
delay: 30s
- name: Run smoke tests
command: ./smoke-test.sh ${deploy_env.target}
onFailure:
command: kubectl rollout undo deployment/app-${deploy_env.target}
- name: Switch traffic
command: |
kubectl patch service app \
-p '{"spec":{"selector":{"version":"${deploy_env.target}"}}}'
- name: Verify switch
command: curl -f https://app.example.com/health
onFailure:
retry: 5
delay: 10s
- name: Scale down old environment
command: kubectl scale deployment/app-${current_env.stdout} --replicas=0
when: params.keepOld != true
Best Practicesβ
1. Use Named Stepsβ
# Good - clear step purposes
steps:
- name: Install dependencies
command: npm install
- name: Run tests
command: npm test
# Bad - unclear steps
steps:
- command: npm install
- command: npm test
2. Handle Errors Appropriatelyβ
steps:
- name: Critical operation
command: important-task
onFailure: abort
- name: Optional enhancement
command: nice-to-have
onFailure: continue
3. Use Parallel Execution Wiselyβ
# Good - independent operations
steps:
- name: Parallel independent tasks
parallel: true
steps:
- command: backup-db
- command: backup-files
- command: backup-configs
# Bad - dependent operations
steps:
- parallel: true
steps:
- command: stop-service
- command: start-service # Depends on stop!
4. Clean Up Resourcesβ
steps:
- name: Main operation
command: create-resources
- name: Cleanup
command: delete-resources
alwaysRun: true
5. Document Complex Logicβ
tasks:
complex-workflow:
description: |
This task performs a rolling update with health checks.
It will automatically rollback on failure.
Expected duration: 15-20 minutes
steps:
# Step descriptions...
Next Stepsβ
- Simple Tasks - Basic task concepts
- Task Overview - Task management concepts
See Alsoβ
- Task Command - Running tasks
- Best Practices - Task design guidelines