Learn through the super-clean Baeldung Pro experience:
>> Membership and Baeldung Pro.
No ads, dark-mode and 6 months free of IntelliJ Idea Ultimate to start with.
Last updated: July 4, 2025
In infrastructure provisioning with Terraform, tags play a crucial role in organizing and managing cloud resources. Tags serve as key-value pairs that help group resources by environment, project, owner, or any other business context. While tags and tags_all attributes deal with tags, Terraform introduces a subtle but important distinction between the two. Understanding tags and tags_all behavior is crucial for maintaining consistency, avoiding configuration drift, and controlling inherited metadata.
In this tutorial, we’ll explore the differences between tags and tags_all in Terraform, how each one works, and when to use them.
In Terraform, tags define metadata for cloud resources. They consist of key-value pairs and commonly apply to resources on platforms like AWS, Azure, GCP, and others. In practice, tags help associate additional context with a resource. For example, we can indicate an environment as dev or prod, using tags.
Additionally, Terraform configures tags within a resource block as part of its arguments. In practice, defining a tags argument explicitly instructs the provider to associate the specified key-value pairs with the resource during creation or update. This tagging mechanism is provider-aware, and many cloud providers automatically expose tags or equivalent attributes for resource metadata. When tags are used correctly, they help streamline automation, governance, and monitoring workflows across infrastructure environments.
The tags attribute in Terraform is used to assign custom metadata to cloud resources. When used in a resource block in Terraform configuration, it instructs the specified provider to attach the set of tags during resource creation or update. For example, let’s assume we want to provision an EC2 instance with Terraform:
resource "aws_instance" "web_server" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t2.micro"
tags = {
Name = "web-server"
Environment = "staging"
Owner = "infra-team"
}
}
In this configuration, Terraform instructs the AWS provider to attach three tags (Name, Environment, and Owner) to the provisioned EC2 instance. These tags used in this example explicitly define the resource categorization (web-server), environment segregation (staging), and ownership tracking (infra-team).
It’s important to note, tags blocks are only applied to a specific resource. Furthermore, tags don’t automatically inherit tags from higher levels, like provider blocks or modules. As a result, we gain precision and control by using tags, but we must manually declare them for each resource to maintain consistent tagging across infrastructure components.
The tag_all attribute represents the final set of tags applied by a provider to a resource after combining user-defined tags with any default tags at higher levels. Unlike tags attributes, which contain only explicitly declared tags within a resource block, tags_all captures both explicitly defined and inherited tags.
Let’s consider a practical example using the AWS provider to provision resources:
provider "aws" {
region = "us-east-1"
default_tags {
tags = {
Project = "CustomerPortal"
Environment = "production"
}
}
}
resource "aws_instance" "web_server" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t2.micro"
tags = {
Name = "web-server"
Owner = "infra-team"
}
}
This configuration consists of a provider-level default_tag defining two tags (Project and Environment), and the EC2 instance defines its own tags block (Name and Owner). Now, when this configuration is applied, Terraform merges the tag definitions to produce the following tags_all output:
{
Name = "web-server"
Owner = "infra-team"
Project = "CustomerPortal"
Environment = "production"
}
As seen, tags_all reflects the final set of tags after Terraform merges user‑defined tags with any defaults at the provider or module level. However, the AWS provider (v3 and newer) treats tags_all as a computed attribute, which evaluates its value during the plan phase rather than reading it directly from the configuration.
Additionally, tags_all can never be declared inside a resource block. Instead, the attribute can still drive drift detection and sometimes trigger plan diffs if the default tags change. Consequently, tags_all is effectively read-only from a configuration standpoint. Yet, Terraform still reconciles any discrepancies it detects at apply time.
We can add or omit tags based on variables, locals, or expressions with Terraform’s interpolation syntax. First, let’s consider an example of dynamic tag values:
variable "env" {
type = string
default = "dev"
}
resource "aws_s3_bucket" "assets" {
bucket = "assets-${var.env}"
tags = {
Environment = var.env
Owner = "frontend-team"
}
}
In this example, the Environment tag dynamically reflects the value of the env variable. This approach enables consistent tagging across environments while using a single module or resource definition.
Additionally, let’s also consider an example that shows conditional tagging:
variable "is_production" {
type = bool
default = false
}
locals {
cost_center = var.is_production ? "CC-1001" : null
}
resource "aws_instance" "web" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t3.micro"
tags = {
Name = "web-${var.is_production ? "prod" : "test"}"
CostCenter = local.cost_center
}
}
In this example, the configuration demonstrates how to include a CostCenter tag only when provisioning production resources conditionally. Here, Terraform automatically excludes any tag with a null value, resulting in a clean and accurate tag set.
Let’s look at the key differences between tags and the tags_all attribute in Terraform:
| tags | tags_all |
|---|---|
| It’s explicitly defined by the user within a resource block | It’s automatically computed by Terraform |
| It specifies only the tags assigned directly to the resource | It represents the complete set of tags, including inherited default tags and explicit tags |
| Does not inherit tags from provider or module-level defaults | It inherits and merges tags from provider-level or module-level default_tags configurations |
| It’s used when specific tags need to be applied to individual resources | It’s used internally by Terraform to calculate the full tag set during planning and applying |
| Terraform applies only these tags to the resource explicitly | Terraform computes this attribute to ensure tag consistency and track changes effectively |
| It’s visible in the configuration and reflected directly in the plan | It’s visible in the plan output but not declared in the configuration |
| It has fine-grained control over resource-specific tagging | It’s used for auditing, drift detection, and consistency enforcement with inherited tags |
| If a key overlaps with a provider‑level default tag, the resource‑level value wins | Shows the resolved value after Terraform applies resource beats the default precedence |
The table summarizes the differences between tags and tags_all.
Furthermore, when a key exists in both a default tag set and a resource-specific tags block, Terraform uses the value from the tags block. tags_all then records that resolved value, ensuring the resource’s explicit intent always takes precedence.
In this article, we examined how Terraform handles tagging through the tags and tags_all attributes. While tags give us explicit control over resource-level metadata, tags_all provides a complete view by including both user-defined and inherited tags.
Understanding their differences helps maintain consistency, enforce tagging standards, and avoid configuration drift.