Skip to main content

Variables and Outputs


Chapter objectives

  • Declare and use variables
  • Understand the types of variables
  • Pass values to variables
  • Export outputs

1 - Input Variables

Basic declaration

# variables.tf

variable "region" {
description = "AWS region"
type = string
default = "eu-west-1"
}

variable "instance_type" {
description = "Type d'instance EC2"
type = string
default = "t2.micro"
}

variable "instance_count" {
description = "Nombre d'instances"
type = number
default = 1
}

variable "enable_monitoring" {
description = "Activer le monitoring"
type = bool
default = false
}

Usage

# main.tf

provider "aws" {
region = var.region
}

resource "aws_instance" "web" {
count = var.instance_count

ami = data.aws_ami.ubuntu.id
instance_type = var.instance_type
monitoring = var.enable_monitoring
}

2 - Types of variables

Primitive types

variable "string_var" {
type = string
default = "hello"
}

variable "number_var" {
type = number
default = 42
}

variable "bool_var" {
type = bool
default = true
}

Collections

# Liste
variable "availability_zones" {
type = list(string)
default = ["eu-west-1a", "eu-west-1b", "eu-west-1c"]
}

# Map
variable "tags" {
type = map(string)
default = {
Environment = "Production"
Team = "DevOps"
}
}

# Set
variable "allowed_ips" {
type = set(string)
default = ["10.0.0.1", "10.0.0.2"]
}

Structural types

# Object
variable "instance_config" {
type = object({
ami = string
instance_type = string
tags = map(string)
})

default = {
ami = "ami-12345678"
instance_type = "t2.micro"
tags = {
Name = "default"
}
}
}

# Tuple
variable "mixed_config" {
type = tuple([string, number, bool])
default = ["hello", 42, true]
}

# Liste d'objets
variable "servers" {
type = list(object({
name = string
type = string
port = number
}))

default = [
{ name = "web", type = "t2.micro", port = 80 },
{ name = "api", type = "t2.small", port = 8080 }
]
}

3 - Variable validation

Validation rules

variable "instance_type" {
type = string
description = "Type d'instance EC2"

validation {
condition = can(regex("^t[23]\\.", var.instance_type))
error_message = "Instance type must be t2.* or t3.*"
}
}

variable "environment" {
type = string
description = "Environnement de déploiement"

validation {
condition = contains(["dev", "staging", "production"], var.environment)
error_message = "Environment must be dev, staging, or production."
}
}

variable "port" {
type = number
description = "Port de l'application"

validation {
condition = var.port >= 1 && var.port <= 65535
error_message = "Port must be between 1 and 65535."
}
}

Multiple validations

variable "bucket_name" {
type = string

validation {
condition = length(var.bucket_name) >= 3
error_message = "Bucket name must be at least 3 characters."
}

validation {
condition = length(var.bucket_name) <= 63
error_message = "Bucket name must not exceed 63 characters."
}

validation {
condition = can(regex("^[a-z0-9][a-z0-9.-]*[a-z0-9]$", var.bucket_name))
error_message = "Bucket name must start and end with lowercase letter or number."
}
}

4 - Passing values

Methods by order of precedence

The terraform.tfvars file

# terraform.tfvars
region = "eu-west-1"
instance_type = "t3.micro"
instance_count = 3
environment = "production"

tags = {
Project = "MyApp"
Environment = "Production"
Team = "Platform"
}

Custom .tfvars file

# production.tfvars
environment = "production"
instance_type = "t3.large"
instance_count = 5

# staging.tfvars
environment = "staging"
instance_type = "t3.small"
instance_count = 2
# Utilisation
terraform plan -var-file="production.tfvars"
terraform apply -var-file="staging.tfvars"

Environment variables

# Format: TF_VAR_<variable_name>
export TF_VAR_region="eu-west-1"
export TF_VAR_instance_type="t3.micro"
export TF_VAR_environment="production"

# Utilisation
terraform plan

Command line

# Une variable
terraform plan -var="instance_type=t3.large"

# Plusieurs variables
terraform apply \
-var="instance_type=t3.large" \
-var="instance_count=5" \
-var="environment=production"

5 - Sensitive variables

Mark as sensitive

variable "db_password" {
description = "Database password"
type = string
sensitive = true
}

variable "api_key" {
description = "API key for external service"
type = string
sensitive = true
}

Behavior

# Le plan masque les valeurs sensibles
Terraform will perform the following actions:

# aws_db_instance.main will be created
+ resource "aws_db_instance" "main" {
+ password = (sensitive value)
}

Best practices

# ✅ Ne jamais mettre de default pour les secrets
variable "db_password" {
type = string
sensitive = true
# Pas de default!
}

# ✅ Utiliser avec des fichiers tfvars (ignorés par git)
# secrets.tfvars (dans .gitignore)
db_password = "super_secret_password"

6 - Local Values

Declaration

locals {
# Valeur simple
environment = "production"

# Valeur calculée
name_prefix = "${var.project}-${local.environment}"

# Tags communs
common_tags = {
Environment = local.environment
Project = var.project
ManagedBy = "Terraform"
}

# Transformation
instance_ids = [for i in aws_instance.web : i.id]

# Conditionnelle
enable_logging = local.environment == "production" ? true : false
}

Usage

resource "aws_instance" "web" {
ami = data.aws_ami.ubuntu.id
instance_type = var.instance_type

tags = merge(local.common_tags, {
Name = "${local.name_prefix}-web"
})
}

resource "aws_s3_bucket" "logs" {
count = local.enable_logging ? 1 : 0

bucket = "${local.name_prefix}-logs"
tags = local.common_tags
}

Difference between variables and locals


7 - Outputs

Basic declaration

# outputs.tf

output "instance_id" {
description = "ID de l'instance EC2"
value = aws_instance.web.id
}

output "public_ip" {
description = "IP publique de l'instance"
value = aws_instance.web.public_ip
}

output "public_dns" {
description = "DNS public de l'instance"
value = aws_instance.web.public_dns
}

Outputs with expressions

# Liste d'IDs
output "instance_ids" {
description = "IDs de toutes les instances"
value = aws_instance.web[*].id
}

# Map d'IPs
output "instance_ips" {
description = "IPs par nom"
value = {
for instance in aws_instance.web :
instance.tags["Name"] => instance.public_ip
}
}

# Valeur conditionnelle
output "load_balancer_dns" {
description = "DNS du load balancer"
value = var.create_lb ? aws_lb.main[0].dns_name : null
}

Sensitive outputs

output "db_password" {
description = "Database password"
value = random_password.db.result
sensitive = true
}

# Affichage
# Outputs:
# db_password = <sensitive>

Output dependencies

output "connection_string" {
description = "Connection string pour la base de données"
value = "postgresql://${var.db_user}:${random_password.db.result}@${aws_db_instance.main.endpoint}"
sensitive = true

# Assurer que la ressource existe avant l'output
depends_on = [aws_db_instance.main]
}

8 - Outputs between modules

Child module

# modules/vpc/outputs.tf
output "vpc_id" {
description = "ID du VPC"
value = aws_vpc.main.id
}

output "public_subnet_ids" {
description = "IDs des subnets publics"
value = aws_subnet.public[*].id
}

output "private_subnet_ids" {
description = "IDs des subnets privés"
value = aws_subnet.private[*].id
}

Parent module

# main.tf
module "vpc" {
source = "./modules/vpc"

cidr_block = "10.0.0.0/16"
}

# Utilisation des outputs du module
resource "aws_instance" "web" {
subnet_id = module.vpc.public_subnet_ids[0]
# ...
}

# Re-export des outputs
output "vpc_id" {
value = module.vpc.vpc_id
}

9 - Viewing outputs

Commands

# Voir tous les outputs après apply
terraform output

# Un output spécifique
terraform output instance_id

# Format JSON
terraform output -json

# Valeur brute (sans quotes)
terraform output -raw public_ip

Example output

$ terraform output
instance_id = "i-0123456789abcdef0"
public_ip = "54.123.45.67"
public_dns = "ec2-54-123-45-67.eu-west-1.compute.amazonaws.com"

$ terraform output -json
{
"instance_id": {
"sensitive": false,
"type": "string",
"value": "i-0123456789abcdef0"
},
"public_ip": {
"sensitive": false,
"type": "string",
"value": "54.123.45.67"
}
}

10 - Best practices

Variables

# ✅ Toujours ajouter une description
variable "instance_type" {
description = "Type d'instance EC2 à utiliser"
type = string
default = "t2.micro"
}

# ✅ Utiliser la validation
variable "environment" {
type = string
validation {
condition = contains(["dev", "staging", "prod"], var.environment)
error_message = "Invalid environment."
}
}

# ✅ Marquer les secrets comme sensitive
variable "db_password" {
type = string
sensitive = true
}

Outputs

# ✅ Description claire
output "load_balancer_url" {
description = "URL du load balancer pour accéder à l'application"
value = "https://${aws_lb.main.dns_name}"
}

# ✅ Exporter ce qui est utile
output "deployment_info" {
value = {
vpc_id = module.vpc.vpc_id
instance_ids = aws_instance.web[*].id
lb_dns = aws_lb.main.dns_name
}
}

Summary

Key points
  • Variables make the configuration parameterizable
  • Use validation for constraints
  • Mark secrets as sensitive
  • Locals simplify the code
  • Outputs expose the important values

Practical exercises

  1. Create variables for the environment (dev/staging/prod)
  2. Add validation on the variables
  3. Use locals for common tags
  4. Export important information via outputs

← Resources | State Management →