Terraform local-exec Provisioner: A Short Guide for Infrastructure Teams
Table of Contents
- What is local-exec?
- Basic Syntax and Configuration
- Common Use Cases with Examples
- local-exec vs remote-exec
- Security Best Practices
- Enterprise Considerations
- Summary Comparison
- Conclusion
What is local-exec?
The Terraform local-exec provisioner executes commands on the machine running Terraform—not on the provisioned resources. While HashiCorp positions provisioners as a "last resort," local-exec provides practical solutions for bridging declarative infrastructure management with imperative operations.
Use local-exec for:
- External system integrations lacking native Terraform providers
- Post-deployment validation and health checks
- Dynamic inventory generation for configuration management
- Local file operations based on infrastructure outputs
Avoid local-exec for:
- Operations with existing Terraform provider support
- Resource configuration (use remote-exec or cloud-init)
- Complex processes better handled by dedicated orchestration tools
Basic Syntax and Configuration
resource "aws_instance" "web" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t3.micro"
provisioner "local-exec" {
command = "echo 'Server deployed: ${self.private_ip}'"
working_dir = "${path.module}/scripts"
environment = {
SERVER_IP = self.private_ip
ENV = var.environment
}
when = "create" # or "destroy"
on_failure = "fail" # or "continue"
}
}
Common Use Cases with Examples
Dynamic Ansible Inventory
resource "aws_instance" "web_servers" {
count = var.server_count
ami = data.aws_ami.amazon_linux.id
# ... configuration ...
}
resource "local_file" "inventory" {
content = templatefile("inventory.tpl", {
servers = aws_instance.web_servers[*].public_ip
})
filename = "ansible/inventory"
provisioner "local-exec" {
working_dir = "ansible"
command = "ansible-playbook -i inventory site.yml"
}
}
Database Migrations
resource "null_resource" "db_migrations" {
depends_on = [aws_db_instance.app_db]
provisioner "local-exec" {
environment = {
DB_HOST = aws_db_instance.app_db.address
DB_NAME = aws_db_instance.app_db.db_name
DB_USER = var.db_username
DB_PASS = var.db_password
}
command = "./run_migrations.sh"
}
}
Health Check Validation
resource "aws_instance" "app" {
# ... configuration ...
provisioner "local-exec" {
command = <<-EOT
for i in {1..30}; do
if curl -f http://${self.public_ip}:8080/health; then
echo "Application ready!"
exit 0
fi
sleep 10
done
exit 1
EOT
}
}
local-exec vs remote-exec
Aspect | local-exec | remote-exec |
---|---|---|
Execution | Terraform runner machine | Target resource |
Network | Local access only | SSH/WinRM to resource |
Use Cases | External integrations, health checks | Resource configuration |
Security | Terraform process permissions | Remote user permissions |
Debugging | Easy local access | Requires resource connectivity |
Security Best Practices
Prevent Shell Injection
# ❌ VULNERABLE
provisioner "local-exec" {
command = "process.sh --input '${var.user_input}'"
}
# ✅ SECURE
provisioner "local-exec" {
environment = {
USER_INPUT = var.user_input
}
command = "process.sh --input \"$USER_INPUT\""
}
Secure Credential Handling
data "aws_secretsmanager_secret_version" "creds" {
secret_id = "api/credentials"
}
resource "null_resource" "secure_task" {
provisioner "local-exec" {
environment = {
API_KEY = jsondecode(data.aws_secretsmanager_secret_version.creds.secret_string).api_key
}
command = "./secure_operation.sh"
}
}
Enterprise Considerations
For organizations running Terraform at scale, local-exec usage benefits from centralized governance. Platforms like Scalr provide enhanced capabilities that address common enterprise challenges:
- Centralized execution with comprehensive audit trails of all local-exec operations
- Policy enforcement to monitor and restrict local-exec usage across teams
- Consistent environments eliminating "works on my machine" issues
- Role-based controls determining who can deploy configurations with local-exec
CI/CD Integration
resource "null_resource" "deployment_notification" {
provisioner "local-exec" {
environment = {
PIPELINE_ID = var.pipeline_id
COMMIT_SHA = var.commit_sha
SLACK_URL = var.notification_webhook
}
command = "./notify_deployment.sh"
}
}
Summary Comparison
Feature | local-exec | remote-exec | Native Provider |
---|---|---|---|
Execution | Local machine | Target resource | Terraform core |
Security Risk | Shell injection | Remote access | Provider-specific |
Debugging | Easy | Moderate | Easy |
Enterprise Fit | Good with governance | Moderate | Excellent |
Maintenance | High | High | Low |
Conclusion
Terraform's local-exec provisioner serves as a practical bridge between declarative infrastructure and imperative operations. While it requires careful security and maintainability considerations, it remains essential for complex automation scenarios.
Key recommendations:
- Use judiciously - Prefer native providers when available
- Secure by default - Use environment variables, never direct interpolation
- Design for scale - Consider centralized execution platforms for enterprise governance
- Handle errors gracefully - Implement timeouts, retries, and cleanup procedures
For enterprise teams, centralized Terraform execution platforms can significantly improve the governance and reliability of local-exec usage while maintaining the flexibility that makes it valuable.