Orchestrate Together
Deployment orchestration
In production environments, you’ll often need to deploy both Terraform and Pulumi stacks together. This section covers patterns for orchestrating deployments, managing dependencies, and sharing configuration.
CI/CD pipeline integration
GitHub Actions example
Let’s consider the example from the Referencing Terraform State step earlier. The configuration below shows a complete GitHub Actions workflow that deploys both the Terraform-managed ECS/ECR backend and the Pulumi-managed application stacks that depend on the backend infrastructure:
# .github/workflows/deploy.yml
name: Deploy Infrastructure
on:
push:
branches: [main]
pull_request:
branches: [main]
env:
AWS_REGION: us-west-2
jobs:
deploy-terraform:
runs-on: ubuntu-latest
outputs:
ecs-cluster-name: ${{ steps.tf-output.outputs.ecs_cluster_name }}
ecr-repository-url: ${{ steps.tf-output.outputs.ecr_repository_url }}
vpc-id: ${{ steps.tf-output.outputs.vpc_id }}
private-subnet-ids: ${{ steps.tf-output.outputs.private_subnet_ids }}
steps:
- uses: actions/checkout@v4
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ env.AWS_REGION }}
- name: Setup Terraform
uses: hashicorp/setup-terraform@v3
with:
terraform_version: 1.5.0
terraform_wrapper: false
- name: Terraform Init
run: terraform init
working-directory: ./terraform
- name: Terraform Plan
run: terraform plan
working-directory: ./terraform
- name: Terraform Apply
if: github.ref == 'refs/heads/main'
run: terraform apply -auto-approve
working-directory: ./terraform
- name: Get Terraform outputs
id: tf-output
run: |
echo "ecs_cluster_name=$(terraform output -raw ecs_cluster_name)" >> $GITHUB_OUTPUT
echo "ecr_repository_url=$(terraform output -raw ecr_repository_url)" >> $GITHUB_OUTPUT
echo "vpc_id=$(terraform output -raw vpc_id)" >> $GITHUB_OUTPUT
echo "private_subnet_ids=$(terraform output -json private_subnet_ids)" >> $GITHUB_OUTPUT
working-directory: ./terraform
deploy-pulumi:
runs-on: ubuntu-latest
needs: deploy-terraform
steps:
- uses: actions/checkout@v4
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ env.AWS_REGION }}
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
- name: Install dependencies
run: npm install
working-directory: ./pulumi
- name: Install Pulumi CLI
uses: pulumi/actions@v4
- name: Deploy Pulumi stack
uses: pulumi/actions@v4
with:
command: up
stack-name: production
work-dir: ./pulumi
env:
PULUMI_ACCESS_TOKEN: ${{ secrets.PULUMI_ACCESS_TOKEN }}
- name: Get Pulumi outputs
id: pulumi-output
run: |
echo "app_url=$(pulumi stack output appUrl)" >> $GITHUB_OUTPUT
echo "service_name=$(pulumi stack output serviceName)" >> $GITHUB_OUTPUT
working-directory: ./pulumi
env:
PULUMI_ACCESS_TOKEN: ${{ secrets.PULUMI_ACCESS_TOKEN }}
test-deployment:
runs-on: ubuntu-latest
needs: [deploy-terraform, deploy-pulumi]
steps:
- name: Test application
run: |
curl -f ${{ needs.deploy-pulumi.outputs.app_url }} || exit 1
echo "Application is responding successfully"
Shared configuration with Pulumi ESC
Pulumi ESC is a powerful tool to share configuration between multiple environments and disparate tools. In brief, you can setup up a Pulumi ESC environment with configuration and secrets, and share that within both your Pulumi and Terraform environments.
You can run the terraform
CLI app with environment variables provided by Pulumi ESC, or generate a var file that can be passed to Terraform.
To learn more, read this in-depth article showing how to integrate Terraform and Pulumi ESC.
Dependency management strategies
Sequential deployment
Deploy stacks in sequence when there are hard dependencies:
#!/bin/bash
# deploy.sh
set -e
echo "Deploying Terraform infrastructure..."
cd terraform
terraform init
terraform plan
terraform apply -auto-approve
cd ..
echo "Deploying Pulumi application..."
cd pulumi
pulumi up --yes
cd ..
echo "Deployment complete!"
Parallel deployment
Deploy independent stacks in parallel for faster deployments:
#!/bin/bash
# parallel-deploy.sh
set -e
# Deploy independent infrastructure in parallel
(
echo "Deploying Terraform networking..."
cd terraform/networking
terraform init
terraform apply -auto-approve
) &
(
echo "Deploying Terraform security..."
cd terraform/security
terraform init
terraform apply -auto-approve
) &
# Wait for parallel deployments
wait
# Deploy dependent application stack
echo "Deploying Pulumi application..."
cd pulumi
pulumi up --yes
echo "Deployment complete!"
Blue-green deployment
Implement blue-green deployments across both tools:
# .github/workflows/blue-green.yml
name: Blue-Green Deployment
on:
push:
branches: [main]
jobs:
deploy-green:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Deploy green environment
run: |
# Deploy Terraform infrastructure for green
cd terraform
terraform workspace select green || terraform workspace new green
terraform apply -auto-approve
# Deploy Pulumi application to green
cd ../pulumi
pulumi stack select green || pulumi stack init green
pulumi up --yes
- name: Test green environment
run: |
# Health check green environment
GREEN_URL=$(cd pulumi && pulumi stack output appUrl)
curl -f $GREEN_URL/health || exit 1
- name: Switch traffic to green
run: |
# Update load balancer to point to green
cd terraform/traffic
terraform apply -auto-approve -var="active_environment=green"
- name: Cleanup blue environment
run: |
# Destroy blue environment after successful switch
cd pulumi
pulumi stack select blue
pulumi destroy --yes
cd ../terraform
terraform workspace select blue
terraform destroy -auto-approve
Monitoring and observability
Unified logging
Configure both Terraform and Pulumi to send logs to the same destination:
# terraform/logging.tf
resource "aws_cloudwatch_log_group" "terraform_logs" {
name = "/terraform/${var.environment}"
retention_in_days = 30
}
resource "aws_cloudwatch_log_stream" "terraform_stream" {
name = "terraform-operations"
log_group_name = aws_cloudwatch_log_group.terraform_logs.name
}
// pulumi/monitoring.ts
import * as aws from "@pulumi/aws";
// Create log group for Pulumi operations
const pulumiLogGroup = new aws.cloudwatch.LogGroup("pulumi-logs", {
name: `/pulumi/${pulumi.getStack()}`,
retentionInDays: 30,
});
// Create log stream
const pulumiLogStream = new aws.cloudwatch.LogStream("pulumi-stream", {
name: "pulumi-operations",
logGroupName: pulumiLogGroup.name,
});
Shared metrics
Use the same metrics namespace for both tools:
# terraform/metrics.tf
resource "aws_cloudwatch_metric_alarm" "terraform_errors" {
alarm_name = "terraform-errors-${var.environment}"
comparison_operator = "GreaterThanThreshold"
evaluation_periods = "2"
metric_name = "ErrorCount"
namespace = "InfrastructureDeployment"
period = "300"
statistic = "Sum"
threshold = "1"
alarm_description = "This metric monitors Terraform errors"
alarm_actions = [aws_sns_topic.alerts.arn]
dimensions = {
Tool = "Terraform"
Environment = var.environment
}
}
// pulumi/metrics.ts
const pulumiErrorAlarm = new aws.cloudwatch.MetricAlarm("pulumi-errors", {
alarmName: `pulumi-errors-${environment}`,
comparisonOperator: "GreaterThanThreshold",
evaluationPeriods: 2,
metricName: "ErrorCount",
namespace: "InfrastructureDeployment",
period: 300,
statistic: "Sum",
threshold: 1,
alarmDescription: "This metric monitors Pulumi errors",
alarmActions: [alertsTopic.arn],
dimensions: {
Tool: "Pulumi",
Environment: environment,
},
});
Best practices
Some best practice tips to keep your two systems orchestrated:
- State management: Use remote state for both Terraform and Pulumi
- Environment isolation: Keep production and dev/test/staging environments completely separated
- Dependency documentation: Clearly document dependencies between stacks
- Testing: Test the output of both tools using integration tests
- Rollback strategy: Have rollback procedures for both tools
- Monitoring: Use common logging and metrics endpoints to monitor both Terraform and Pulumi deployments
- Security: Use a single tool to manage secrets across both tools
- Version control: Tag releases that include both Terraform and Pulumi changes to create a unified release
Thank you for your feedback!
If you have a question about how to use Pulumi, reach out in Community Slack.
Open an issue on GitHub to report a problem or suggest an improvement.