Deploy a FinOps Foundation in 12 Minutes with Terraform
The 12-minute claim is real โ here is the exact variable set, the expected terraform apply output, and how to verify each component worked. Plus the optional AI + security monitoring layer that adds 3 minutes.
The typical barrier to FinOps tooling is not complexity โ it is the setup cost. Configuring billing exports, creating Athena tables, wiring budget alerts, and deploying Lambda functions individually takes a day of console clicking and produces infrastructure that is inconsistent across accounts and difficult to audit. Terraform collapses all of that into a single apply.
This article covers what a minimal FinOps Terraform module deploys, the exact commands to run, the expected output, and how to verify each component is working. The clock starts when you run terraform init.
What Gets Deployed
The minimal foundation covers four layers:
| Layer | Resources | Purpose |
|---|---|---|
| Billing ingestion | S3 bucket (versioned, private), bucket policy for AWS Data Exports delivery | Receives FOCUS 1.2 billing exports from AWS |
| Catalog | Glue database, Glue crawler (scheduled 3ร/day), IAM role for crawler | Keeps Athena table schema current as AWS adds FOCUS columns |
| Query layer | Athena workgroup (100GB scan cap, encrypted results), results prefix in S3 | Queryable billing data โ the foundation for all detection queries |
| Alerting | SNS topic, email subscription, 4 AWS Budgets (OpenSearch OCU, Bedrock, SageMaker, total account), Cost Anomaly Detection monitor + subscription | Catches floor costs, behavioral changes, and total account overruns |
Optional add-on (3 additional minutes): Lambda security sensors โ orphaned KB scanner, new-region detector, AI tagging enforcer โ with EventBridge schedules and the IAM role covering all three.
Prerequisites (2 minutes before the clock starts)
- AWS CLI configured:
aws sts get-caller-identityshould return your account ID without error - Terraform โฅ 1.5:
terraform version - The account must have Cost Explorer enabled (AWS Console โ Cost Management โ Cost Explorer โ enable if not already on)
- An email address you can confirm a subscription for โ SNS sends a confirmation email immediately after apply
The Variable Set
Create a terraform.tfvars file with these values โ this is everything the module needs:
# terraform.tfvars
prefix = "finops" # prepended to all resource names
environment = "prod" # used in tags and resource names
aws_region = "us-east-1" # region for all resources
alert_email = "your-team@example.com" # SNS subscription target
# Monthly budget thresholds (USD) โ set to 2ร current monthly average for each
monthly_account_budget_usd = 5000
bedrock_monthly_budget_usd = 200
sagemaker_monthly_budget_usd = 500
# OpenSearch OCU budget is fixed at $50 โ catches KB orphan in 4-5 days
# Athena scan cap per query (bytes) โ 100GB = $0.50 max per query at $5/TB
athena_bytes_scanned_cutoff = 107374182400
# Optional: deploy Lambda security sensors (KB scanner, new-region, tagging enforcer)
deploy_security_lambdas = true
The Commands
# 1. Initialize โ downloads providers, sets up state (30โ60 seconds)
terraform init
# 2. Validate โ catches syntax errors before any AWS calls (5 seconds)
terraform validate
# 3. Plan โ shows exactly what will be created, no changes made yet (20โ30 seconds)
terraform plan -var-file=terraform.tfvars
# 4. Apply โ creates all resources (8โ10 minutes)
terraform apply -var-file=terraform.tfvars
The apply step takes 8โ10 minutes because AWS Budgets, Glue crawlers, and Cost Anomaly Detection monitors each have creation latency of 30โ90 seconds. The SNS topic and S3 bucket create in under 10 seconds each. The Lambda functions (if enabled) deploy in 15โ30 seconds each.
Expected Output
A successful terraform apply ends with output like this:
Apply complete! Resources: 28 added, 0 changed, 0 destroyed.
Outputs:
athena_database = "finops_billing"
athena_workgroup = "finops-billing"
billing_bucket = "finops-billing-exports-123456789012"
sns_topic_arn = "arn:aws:sns:us-east-1:123456789012:cost-alerts-prod"
# If deploy_security_lambdas = true:
kb_scanner_function = "finops-kb-orphan-scanner-prod"
new_region_detector = "finops-new-region-detector-prod"
tagging_enforcer_function = "finops-ai-tagging-enforcer-prod"
If you see any errors, the two most common are:
AccessDeniedonbudgets:CreateBudget: The IAM user or role running Terraform needs thebudgets:CreateBudget,budgets:ModifyBudget, andce:CreateAnomalyMonitorpermissions. These are billing-management actions not included in standard developer policies.- SNS topic policy conflicts: If you already have an SNS topic with the same name from a previous apply, the policy update may fail. Run
terraform destroy -target=aws_sns_topic_policy.budgets_publishthen re-apply.
Verification (3 minutes after apply)
Check each layer completed correctly:
# 1. Confirm S3 bucket exists and is private
aws s3api get-bucket-acl --bucket finops-billing-exports-ACCOUNT_ID
# 2. Confirm Glue crawler exists
aws glue get-crawler --name finops-billing-crawler
# 3. Run a test Athena query (will return empty until first billing export delivers)
aws athena start-query-execution \
--query-string "SELECT COUNT(*) FROM finops_billing.focus_billing" \
--work-group finops-billing \
--query-execution-context Database=finops_billing
# 4. Confirm budgets were created
aws budgets describe-budgets --account-id $(aws sts get-caller-identity --query Account --output text)
# 5. Check your email โ SNS sends a subscription confirmation within 60 seconds
# You must click the confirmation link or alerts will not deliver
Important: The Athena table will be empty until the first FOCUS billing export is delivered. AWS delivers the first export within 24 hours of enabling Data Exports. After that, exports arrive up to 3 times daily. Run the test query again after 24 hours โ you should see row counts matching your account's billing activity.
After the 12 Minutes
The foundation is running. Three things to do in the next 24 hours:
- Confirm the SNS email subscription. Check your inbox for the AWS notification email and click the confirmation link. Unconfirmed subscriptions receive no alerts.
- Enable the FOCUS 1.2 Data Export in the AWS console. Terraform deploys the receiving infrastructure but cannot create the Data Export itself (AWS does not expose this via Terraform provider as of FOCUS 1.2 GA). AWS Console โ Cost Management โ Data Exports โ Create export โ FOCUS 1.2 with AWS columns โ point at the S3 bucket from the Terraform output.
- Verify first export delivery. After 24 hours, run the row count query. Confirm data is flowing before building dashboards or adding detection queries on top.
Find out which layers you still need
The DropInFinOps free assessment maps your current setup against the six-phase checklist โ showing which components are already in place and which gaps are exposing you to undetected AI waste and security billing events.
Take the free assessment โ