Hey DevOps crew! 🚀 Welcome back to our Terraform adventure! In Part 3, we cracked the code on backends and state management—getting cozy with local setups and powering up with remote options. Now, in Part 4, we’re supercharging your skills with Variables, Outputs, and Dynamic Workflows. Get ready to make your configs flexible, reusable, and crystal clear—whether you’re tweaking values on the fly or sharing key details with your team. Let’s unlock the next level of Terraform mastery together—dive in with me!
💬 Got Questions?
If you have any questions or need further clarification while reading this post, please don't hesitate to drop a comment below! I'm here to help, and I'll gladly create new posts to dive deeper into any topics you find challenging. 😊
1. Variables & Outputs
In Terraform, variables and outputs are fundamental constructs that enhance the configurability and observability of infrastructure as code (IaC). Variables provide a mechanism to parameterize configurations, enabling abstraction and reusability across diverse deployment scenarios. By externalizing specific values, they facilitate the adaptation of infrastructure definitions without necessitating modifications to the core codebase. Outputs, conversely, serve as a structured means to expose computed or resultant attributes of provisioned resources, supporting integration with external systems or documentation needs. Collectively, these features underpin Terraform’s capacity to deliver modular, maintainable, and scalable infrastructure management. There are three key types of variables:
1.1. Input Variables
Input variables constitute a core mechanism in Terraform for enabling parameterization of infrastructure configurations. They serve as user-defined entry points, allowing external values to be injected into Terraform modules or configurations at runtime. These variables are referenced within the configuration using the syntax var., providing a standardized method to dynamically tailor resource definitions without altering the underlying codebase.
Definition and Purpose
Input variables facilitate the abstraction of configuration-specific details, such as resource sizes, regions, or environment identifiers, into configurable parameters. This abstraction enhances the reusability and portability of Terraform code across multiple deployment contexts—e.g., development, staging, or production environments. By decoupling hardcoded values from the configuration logic, input variables empower users to adapt infrastructure deployments to varying requirements efficiently and consistently.
Declaration and Usage
Input variables are declared within a Terraform configuration file (typically variables.tf) using the variable block. For example: Defining an Input Variable
variable "instance_type" {
description = "Type of EC2 instance"
type = string
default = "t2.micro"
}
In the main configuration main.tf
, this variable can be referenced as follows:
resource "aws_instance" "web" {
instance_type = var.instance_type
}
Here, var.region retrieves the value supplied by the user, falling back to the default (us-east-1) if none is provided. Values can be passed via command-line flags (e.g., terraform apply -var "region=us-west-2"), variable files, or environment variables, offering flexible input methods.
Key Attributes
- Type Constraints: Variables can specify types (e.g., string, number, list) to enforce data consistency.
- Default Values: Optional defaults provide fallback behavior, reducing mandatory user input.
- Description: Metadata enhances documentation, aiding collaboration and maintainability.
Applications
Input variables are particularly valuable in scenarios requiring customization, such as defining cloud provider regions, instance types, or resource counts. They are foundational to creating reusable modules, where the same configuration can be instantiated with different parameters tailored to specific use cases.
Considerations
While powerful, input variables require careful naming and scoping to avoid conflicts, especially in large-scale projects with multiple modules. Proper documentation of their purpose and expected values is essential for effective team collaboration.
1.2. Local Variables
Local variables in Terraform provide a mechanism for defining intermediate values—either constants or computed expressions—within a configuration. These variables are scoped locally to a specific module or configuration file, enabling better code organization, reducing redundancy, and enhancing maintainability. Referenced using the syntax local., local variables serve as reusable building blocks within the configuration logic, streamlining complex infrastructure definitions.
Definition and Purpose
Local variables are designed to encapsulate values that are derived from input variables, resource attributes, or static constants, thereby avoiding repetitive hardcoding throughout the configuration. By centralizing these values, they minimize duplication (adhering to the DRY—Don’t Repeat Yourself—principle) and improve readability. This abstraction is particularly useful for managing recurring patterns, intermediate calculations, or configuration-specific constants that do not require external user input.
Declaration and Usage
Local variables are defined within a locals block in a Terraform configuration file (e.g., main.tf or a dedicated locals.tf). For example: `Defining Local Variables
hcl
locals {
service_name = "web-app"
owner = "devops-team"
}
Usage:
hcl
tags = {
Name = local.service_name
Owner = local.owner
}
In this example, local.project_name defines a constant, local.full_instance_name computes a composite value, and local.common_tags groups reusable metadata. These can then be referenced via local. in resource definitions, ensuring consistency across the configuration.
Key Attributes
- Scope: Local variables are confined to the module or file where they are defined, preventing unintended global access.
- Flexibility: They can incorporate static values, expressions, or references to other variables (e.g., var. or data.).
- Immutability: Once defined, their values are fixed within a single Terraform execution, ensuring predictable behavior.
Applications
Local variables excel in scenarios requiring code simplification or standardization. For instance, they can consolidate tags applied to multiple resources, compute resource names dynamically based on inputs, or define environment-specific constants. They are especially valuable in larger configurations or reusable modules, where maintaining clarity and reducing repetition are critical.
Considerations
While local variables enhance organization, over-reliance on complex expressions within locals blocks can obscure logic, making debugging more challenging. Best practice dictates using them for straightforward computations or constants, reserving intricate transformations for resource or data blocks where appropriate.
1.3. Output Variables
Output variables in Terraform serve as a mechanism to expose specific values or attributes of provisioned infrastructure following the execution of a configuration. They enable practitioners to retrieve and utilize critical details—such as resource IDs, IP addresses, or computed results—post-deployment. Referenced externally or within modular workflows, output variables enhance the observability and interoperability of Terraform-managed infrastructure, facilitating integration with other tools, scripts, or team processes.
Definition and Purpose
Output variables act as a structured interface between Terraform’s internal state and external consumers, whether human operators, automation pipelines, or downstream modules. By defining what information should be surfaced after an apply operation, they provide a controlled means to extract and share key infrastructure properties without requiring direct access to the state file. This capability is essential for documenting deployment outcomes, enabling cross-module dependencies, and supporting operational workflows in a DevOps context.
Declaration and Usage
Output variables are defined within an output block in a Terraform configuration file (e.g., outputs.tf or main.tf). For example: Defining an Output Variable
`hcl
resource "aws_instance" "web" {
ami = "ami-12345678"
instance_type = "t2.micro"
}
output "instance_id" {
value = aws_instance.web.id
description = "The ID of the deployed EC2 instance"
}
output "public_ip" {
value = aws_instance.web.public_ip
description = "The public IP address of the EC2 instance"
}
`
After running terraform apply, these outputs are displayed in the CLI and can be queried later using terraform output instance_id or accessed programmatically. In modular setups, outputs from one module can be passed as inputs to another, fostering reusability.
sh
terraform output
Key Attributes
- Value Specification: Outputs can reference resource attributes (e.g., aws_instance.web.id), local variables, or computed expressions.
- Description: Optional metadata clarifies the output’s purpose, aiding documentation and team understanding.
- Accessibility: Outputs are available post-execution via CLI (terraform output) or as part of the state file, supporting both manual and automated use cases.
Applications
Output variables are invaluable for scenarios requiring visibility into deployment results. Common use cases include retrieving the IP address of a server for SSH access, exporting a database endpoint for application configuration, or passing a resource ARN to a CI/CD pipeline. In modular architectures, they enable dependency chaining—e.g., a networking module outputting a subnet ID for use in a compute module—enhancing modularity and collaboration.
Considerations
While output variables improve transparency, excessive use can clutter the CLI output or expose unnecessary details, potentially compromising security if sensitive data (e.g., credentials) is inadvertently included. Best practice recommends limiting outputs to essential, non-sensitive values and leveraging the sensitive = true attribute when needed to suppress display in logs.
2. Setting Input Variables
Terraform offers a variety of methods to assign values to input variables, enabling flexibility in how configurations are parameterized across different use cases. These methods are prioritized by precedence, from lowest to highest, allowing higher-priority sources to override lower ones. This hierarchy ensures deterministic behavior in both manual and automated workflows. Below, the primary approaches are outlined in order of increasing precedence: manual entry, default values, and environment variables.
2.1. Manual Entry During terraform apply
- Description: During execution of terraform apply, Terraform prompts the user to manually input values for undefined variables without defaults.
- Mechanism: If a variable (e.g., var.instance_type) lacks a value, Terraform halts and requests input:
bash
$ terraform apply
var.instance_type: Enter a value: t3.medium
- Precedence: Lowest—overridden by all other methods.
- Use Case: Suitable for initial testing or one-time deployments requiring immediate flexibility.
- Considerations: This method’s interactive nature renders it incompatible with automation, as it necessitates human intervention, undermining repeatability and integration with scripted workflows.
2.2. Default Value in Variable Declaration Block
- Description: A fallback value is specified within the variable block using the default attribute.
- Mechanism: Defined in the configuration (e.g., variables.tf):
hcl
variable "instance_type" {
type = string
default = "t3.medium"
}
Terraform uses this value unless overridden by a higher-precedence source.
- Precedence: Second lowest—supersedes manual entry but is overridden by explicit inputs.
- Use Case: Effective for establishing baseline configurations, minimizing user input for common scenarios.
- Considerations: Defaults should reflect typical or safe values to prevent unintended behavior if not explicitly overridden.
2.3. Environment Variables (TF_VAR_<name>)
- Description: Values are supplied via environment variables prefixed with TF_VAR_ followed by the variable name.
- Mechanism: Set in the shell before execution:
sh
export TF_VAR_instance_type="t3.medium"
terraform apply
Terraform automatically detects and applies this value.
- Precedence: Medium—overrides defaults but is superseded by file-based or CLI inputs.
- Use Case: Ideal for CI/CD pipelines or automated deployments where environment variables provide a secure, system-level configuration method.
- Considerations: Requires precise naming and secure management of sensitive values (e.g., via secret stores) to avoid exposure.
2.4. terraform.tfvars
File
- Description: A dedicated file (terraform.tfvars) stores variable assignments, loaded automatically by Terraform if present.
- Mechanism: Example content:
hcl
instance_type = "t3.large"
Place this file in the working directory, and Terraform applies its values during execution.
- Precedence: Higher than environment variables—overridden by .auto.tfvars or CLI inputs.
- Use Case: Useful for managing multiple environments (e.g., staging, production) by maintaining separate .tfvars files per context.
- Considerations: File-based storage simplifies bulk variable assignment but requires careful version control to avoid conflicts or unintended overrides.
2.5. *.auto.tfvars
Files
- Description: Files with the .auto.tfvars extension (e.g., prod.auto.tfvars) are automatically loaded without explicit specification.
- Mechanism: Example:
hcl
instance_type = "t2.large"
Terraform scans the directory and applies all matching files.
- Precedence: Above terraform.tfvars—overridden only by CLI arguments.
- Use Case: Streamlines workflows by enabling environment-specific configurations (e.g., dev.auto.tfvars, prod.auto.tfvars) without manual referencing.
- Considerations: Naming conventions must be consistent, and care should be taken to avoid overlapping variable definitions across multiple .auto.tfvars files.
2.6. Command-Line Arguments (-var or --var-file)
- Description: Values are passed directly via CLI flags (-var) or sourced from a specified file (--var-file), offering the highest level of override control.
- Mechanism: Examples:
sh
terraform apply -var="instance_type=t3.micro"
terraform apply --var-file="prod.tfvars"
The -var flag sets individual variables, while --var-file references a file (e.g., prod.tfvars) with multiple assignments.
- Precedence: Highest—overrides all other methods.
- Use Case: Perfect for dynamic overrides during execution, such as testing alternate configurations or enforcing specific values in automated scripts.
- Considerations: Provides maximum flexibility but requires explicit invocation, making it less suited for persistent configurations compared to file-based methods.
3. Variable Types & Validation
Terraform provides robust mechanisms for defining variable types and enforcing validation rules, ensuring data integrity and configuration reliability within infrastructure as code (IaC). By specifying types and custom constraints, practitioners can enforce consistency, prevent misconfigurations, and enhance the robustness of Terraform configurations. These features are particularly valuable in complex deployments, where input accuracy directly impacts operational success.
3.1. Variable Types
Terraform supports a range of data types for input variables, allowing precise control over the structure and format of values passed to configurations. Common types include:
- Primitive Types:
string
(e.g.,"us-east-1"
),number
(e.g.,2
),bool
(e.g.,true
). - Complex Types:
list(<type>)
(e.g.,["a", "b"]
),map(<type>)
(e.g.,{ key = "value" }
),object({...})
(custom structured data), andtuple([...])
(fixed-length, mixed-type sequences).
Specifying a type
attribute in a variable
block enforces that inputs conform to the declared type, raising errors during terraform plan
or apply
if mismatches occur. This type safety mitigates runtime errors and improves configuration predictability.
3.2. Validation Rules
Beyond type enforcement, Terraform’s validation
block allows the definition of custom logical conditions to constrain variable values further. This feature, introduced in Terraform 0.13 and later, enables fine-grained control by validating inputs against predefined criteria, with customizable error messages to guide users when constraints are violated.
Example: Defining a List with Validation
Consider a scenario where an EC2 instance type must be restricted to an approved set of values. The following configuration demonstrates both type specification and validation:
`hcl
variable "allowed_instance_types" {
type = list(string)
default = ["t2.micro", "t3.small", "t3.medium"]
description = "List of permitted EC2 instance types"
}
variable "instance_type" {
type = string
description = "Instance type for the EC2 instance"
validation {
condition = contains(var.allowed_instance_types, var.instance_type)
error_message = "Invalid instance type! Choose from: t2.micro, t3.small, t3.medium."
}
}
`
Breakdown:
-
allowed_instance_types
: Declares a list(string) with a default set of acceptable instance types. -
instance_type
: Specifies a string type and uses a validation block. -
condition
: The contains() function checks if var.instance_type exists within var.allowed_instance_types. -
error_message
: Provides feedback if the condition fails (e.g., if instance_type = "m5.large" is input).
When executed (e.g., terraform apply -var="instance_type=m5.large"
), Terraform evaluates the condition during planning. If the input is invalid, it halts and displays the error message, ensuring only permitted values are applied.
Key Features
- Type Enforcement: The type attribute rejects structurally invalid inputs (e.g., a number for a string variable).
- Custom Logic: The validation block supports arbitrary boolean expressions using Terraform’s expression syntax (e.g., length(), regex()).
- User Feedback: Descriptive error_message fields improve usability by clarifying acceptable inputs.
Applications
Variable types and validation are critical for:
- Consistency: Ensuring resources adhere to organizational standards (e.g., approved instance types).
- Error Prevention: Catching invalid inputs early in the planning phase, reducing deployment failures.
- Documentation: Embedding constraints in code serves as self-documenting guidance for users or teams.
Considerations
- Complexity: Overly intricate validation logic may obscure intent and complicate maintenance—keep conditions concise and purposeful.
- Flexibility vs. Restriction: Striking a balance is key; excessive constraints may limit legitimate use cases, while lax rules risk misconfiguration.
- Version Dependency: Validation requires Terraform 0.13+, necessitating compatibility checks in legacy environments.
By leveraging variable types and validation, Terraform configurations gain a layer of rigor and reliability, aligning with best practices for scalable and maintainable IaC.
4. Handling Sensitive Data
In Terraform, input variables often need to manage sensitive data—such as passwords, API keys, or other credentials—required for infrastructure provisioning. Given the security implications of exposing such information, Terraform provides mechanisms to handle these secrets securely, minimizing the risk of unintended disclosure in logs, state files, or version control systems. This section outlines approaches to safeguard sensitive data, ranging from built-in features to integration with external secret management systems, with an emphasis on operational security and best practices.
4.1. Marking Variables as Sensitive
- Description: Terraform allows variables to be flagged as sensitive using the sensitive attribute, suppressing their display in CLI output and logs.
- Mechanism: Define the variable with sensitive = true:
hcl
variable "db_password" {
description = "Database password"
type = string
sensitive = true
}
When terraform apply
or terraform plan
runs, the value of var.db_password is masked (e.g., displayed as [sensitive]) rather than shown in plain text.
- Security Benefit: Reduces accidental exposure in terminal outputs or captured logs, enhancing operational security.
- Limitations: The sensitive flag only obscures output—it does not encrypt the value in the state file, which remains a potential exposure point if not secured separately.
- Use Case: Suitable for basic protection in development or controlled environments, but insufficient alone for production-grade security.
4.2. Using Environment Variables (TF_VAR_)
Description: Sensitive values can be injected via environment variables prefixed with TF_VAR_, avoiding hardcoding in configuration files.
Mechanism: Set the variable in the shell:
sh
export TF_VAR_db_password="securepassword123"
terraform apply
Terraform reads this value during execution without storing it in the codebase.
- Security Benefit: Keeps secrets out of version control, reducing the risk of accidental commits to repositories.
- Considerations: Environment variables must be managed securely (e.g., not logged in shell history) and are ephemeral, requiring setup for each session or automation script.
- Use Case: Effective for CI/CD pipelines where secrets can be injected via secure environment variable stores (e.g., GitHub Secrets, Jenkins Credentials).
4.3. Passing via Command-Line Arguments
Description: Values can be passed directly during execution using the -var flag, bypassing configuration files entirely.
Mechanism: Execute Terraform with the sensitive value:
sh
terraform apply -var="db_password=securepassword123"
- Security Benefit: Avoids embedding secrets in tracked files, offering a dynamic input method.
- Limitations: Values may appear in command history or process lists (e.g., ps output), posing a risk if the system is not locked down. Hardcoding in scripts negates this benefit and should be avoided.
- Use Case: Useful for one-off overrides or testing, but less practical for repeated or automated use due to manual entry and potential exposure.
4.4. Integrating with a Secret Store (Best Practice)
- Description: Rather than defining sensitive data in Terraform, external secret management systems—such as AWS Secrets Manager, HashiCorp Vault, or AWS Systems Manager Parameter Store—are used to store and retrieve secrets dynamically.
- Mechanism: Configure a provider to fetch the secret at runtime. Example with AWS Secrets Manager:
`hcl
data "aws_secretsmanager_secret_version" "db_password" {
secret_id = "my-db-password"
}
resource "aws_db_instance" "database" {
password = data.aws_secretsmanager_secret_version.db_password.secret_string
}
`
Terraform retrieves the secret during execution without embedding it in the configuration.
- Security Benefit: Secrets are encrypted at rest, access-controlled via IAM policies or Vault roles, and never stored in Terraform state or code, significantly reducing exposure risk.
- Considerations: Requires additional setup (e.g., secret store configuration, provider authentication) and introduces external dependency, but this overhead is justified by enhanced security.
- Use Case: The recommended approach for production environments, ensuring compliance with security standards and supporting scalable, team-based workflows.
4.5. Security Considerations
- State File Protection: Even with sensitive = true, secrets in the state file remain unencrypted unless the backend (e.g., S3 with server-side encryption) secures it.
- Precedence: Command-line arguments override other methods, so ensure secure handling to avoid accidental leaks.
- Best Practice: Combine sensitive variables with a secret store and a secure remote backend (e.g., S3 with DynamoDB locking) for a defense-in-depth strategy.
By employing these methods—particularly external secret stores—Terraform practitioners can manage sensitive data with the rigor demanded by modern security requirements, balancing usability with robust protection.
Demo
You can find the complete Terraform configurations for this tutorial in the GitHub repository below. Feel free to explore, fork, and experiment! 🚀
🔗 GitHub Repo: [https://github.com/rahimbtc1994/terraform-intermediate/tree/main/part-4]
Now that you've learned how to use variables, outputs, and dynamic workflows in Terraform, it's time to level up your modularization skills! In Terraform for DevOps: Mastering Modules for Scalable Infrastructure (Part 5), we'll dive into Terraform modules—what they are, why they're essential, and how to structure them for scalability and maintainability. Keep refining your Terraform workflow—let's build smarter! 🚀
If you have any questions or run into issues, drop a comment—I’m happy to help! 😊
Top comments (1)
Please feel free to ask any questions you have.