The Terraform ternary operator is a compact way to add conditional logic directly inside your configuration. It lets you choose between two values based on a boolean expression, which is helpful when you want different settings for environments like dev and prod without duplicating whole resources.
In this article, we’ll show you how to use the Terraform ternary operator in real configurations.
What is the Terraform ternary operator?
The Terraform ternary operator is a conditional expression that returns one of two values based on a boolean condition. It follows the format:
condition ? value_if_true : value_if_falseTerraform evaluates the condition first. If it is true the expression resolves to the first value. If it is false it resolves to the second. Both values must be of the same or a compatible type so Terraform can understand the expression during planning.
This operator is typically used to set resource arguments, variable defaults, module inputs, or meta arguments such as count or for_each that depend on feature flags or environment settings.
How does the Terraform ternary operator compare to if/else in programming languages?
In most programming languages, an if / else block controls the flow of execution. It can wrap multiple lines, declare variables, and run different logic. The Terraform ternary operator is always an expression. It produces a single value that you can plug into an argument.
Terraform does not have an if / else statement for resources. You can only use the ternary operator where Terraform expects a value, for example inside a resource argument, a local, a variable default, or a meta argument such as count.
Example 1: Enabling S3 access logging only in production
Imagine you want S3 access logging enabled in production for security and audit reasons, but you want to keep development environments light and cheap.
The Terraform ternary operator lets you express this decision in one place without duplicating entire resource blocks.
variable "environment" {
type = string
default = "dev"
}
resource "aws_s3_bucket" "main" {
bucket = "${var.environment}-app-bucket"
}
resource "aws_s3_bucket" "logs" {
bucket = "${var.environment}-access-logs"
}
resource "aws_s3_bucket_logging" "this" {
count = var.environment == "prod" ? 1 : 0
bucket = aws_s3_bucket.main.id
target_bucket = aws_s3_bucket.logs.id
target_prefix = "logs/"
}Here, the aws_s3_bucket_logging resource uses the Terraform ternary operator in the count meta argument, so the resource is only created in production.
In production, count is 1 so Terraform creates the logging resource that sends access logs to the logs bucket with the “logs/” prefix.
In non-production environments, the count is 0 so Terraform skips this resource, and there is no access logging. This keeps the logging behavior tied to a single variable, avoids the need for separate resources for each environment, and keeps the plan easy to read.
When you change the environment variable from dev to prod, logging turns on automatically without any other code changes.
Example 2: Choosing instance type based on a performance flag
In many teams, developers want a small instance type for most stacks and a bigger instance type only when they run performance tests. You can model this behavior with a boolean variable and the Terraform ternary operator, so that the instance type is derived from a single, clear input.
variable "use_large_instance" {
type = bool
default = false
}
resource "aws_instance" "app" {
ami = "ami-1234567890abcdef0"
instance_type = var.use_large_instance ? "m6i.large" : "t3.micro"
tags = {
Name = var.use_large_instance ? "app-perf-test" : "app-standard"
}
}In this example, the ternary operator appears in both the instance_type and the Name tag. The condition is var.use_large_instance.
- When this flag is true, Terraform picks the
"m6i.large"instance type and the"app-perf-test"tag, which clearly marks the instance as part of a performance test environment. - When the flag is false, Terraform uses the smaller
"t3.micro"instance type and the"app-standard"tag for everyday usage.
This pattern gives you a single switch that controls both cost and labeling, keeps your configuration readable, and makes it obvious in the console which instances are for performance testing and which ones are standard. It also keeps the logic close to the resource that depends on it, which is helpful when you revisit the code months later.
In this and later examples, the ami value is a placeholder that you would replace with a real AMI ID in your own configuration.
How to use multiple ternary operators
Using multiple ternary operators in Terraform primarily involves two patterns: chaining conditions on a single line and nesting them with parentheses to maintain clear intent.
If you are going to use multiple ternary operators in Terraform, keep these points in mind:
- Make sure all branches resolve to the same type, for example, all strings or all numbers.
- Use indentation and parentheses to show structure, not just one very long line.
- If the expression starts to feel hard to read out loud, move it into locals or replace it with a map lookup.
Chaining multiple ternary operators for several environments
You can chain ternary operators when you want one expression that handles more than two cases. A common example is picking an instance type based on the environment.
variable "environment" {
type = string
default = "dev"
}
resource "aws_instance" "app" {
ami = "ami-1234567890abcdef0"
instance_type = var.environment == "prod" ? "m6i.large" :
var.environment == "staging" ? "t3.medium" :
"t3.micro"
tags = {
Name = "app-${var.environment}"
}
}Here, the Terraform ternary operator appears twice inside instance_type. Terraform evaluates it from top to bottom.
If environment is prod, the first condition is true and the result is "m6i.large". If it is not prod but is staging, the first branch fails, the second condition is checked, and the result becomes "t3.medium". For any other value, Terraform falls through to "t3.micro".
The important part is that all three possible results are strings. With multiple ternary operators in Terraform, both branches of every ternary must resolve to the same type. This pattern is a compact way to replace an if / else if / else chain when the logic is simple and you just need one value.
Nesting ternary operators with parentheses
Sometimes you want slightly more structure, or you want the second decision to depend on a different flag. In that case, nested ternary operators with parentheses keep the expression readable.
variable "environment" {
type = string
default = "dev"
}
variable "high_availability" {
type = bool
default = false
}
locals {
app_instance_type = var.environment == "prod"
? (var.high_availability ? "m6i.xlarge" : "m6i.large")
: "t3.micro"
}
resource "aws_instance" "app" {
ami = "ami-1234567890abcdef0"
instance_type = local.app_instance_type
tags = {
Name = "app-${var.environment}"
}
}In this example, the outer ternary checks whether you are in production.
For non-production environments, Terraform uses "t3.micro" for app_instance_type. For production, Terraform then looks at the high_availability flag and chooses either "m6i.xlarge" or "m6i.large".
The parentheses make it obvious that the inner ternary belongs to the true branch of the outer one. This pattern is useful when you have one primary decision, such as environment, and a secondary decision, such as a performance or high availability toggle.
Using a local like app_instance_type also keeps your resource block clean and makes the logic easier to test and revisit later.
Key points
The Terraform ternary operator is a conditional expression that lets you choose between two values based on a boolean condition.
A good practice is to use the Terraform ternary operator for simple, local decisions and avoid nesting it deeply. If you find yourself stacking ternaries inside ternaries, it usually means the logic wants to live in a locals block, a separate variable, or even a for expression or dynamic block. Simple ternary expressions keep resources readable.
For more complex conditional behavior, it is often easier to use count or for_each to control whether resources exist at all, and functions such as coalesce or try to pick fallback values, or even separate resources for each scenario.
Terraform is really powerful, but to achieve an end-to-end secure GitOps approach, you need to use a product that can run your Terraform workflows. Spacelift takes managing Terraform to the next level by giving you access to a powerful CI/CD workflow and unlocking features such as:
- Policies (based on Open Policy Agent)
- Multi-IaC workflows
- Self-service infrastructure
- Integrations with any third-party tools
If you want to learn more about Spacelift, create a free account today or book a demo with one of our engineers.
Note: New versions of Terraform are placed under the BUSL license, but everything created before version 1.5.x stays open-source. OpenTofu is an open-source version of Terraform that expands on Terraform’s existing concepts and offerings. It is a viable alternative to HashiCorp’s Terraform, being forked from Terraform version 1.5.6.
Discover better way to manage Terraform
Spacelift helps manage Terraform state, build more complex workflows, supports policy as code, programmatic configuration, context sharing, drift detection, resource visualization and many more.
