CLI tool
env0/terratag avatar
env0/terratag

Terratag rewrites your Terraform files instead of tagging your cloud

Terratag is a CLI tool that enables users of Terraform to automatically create and maintain tags across their entire set of AWS, Azure, and GCP resources

1,058 stars50 forksGoMPL-2.0

At a glance

What is it?
Terratag is a Go CLI that applies tags and labels across a whole set of OpenTofu or Terraform files, merging them into the resources as HCL and writing a .terratag.tf copy alongside the originals. It fits teams retro-fitting tags onto existing infrastructure as code, and it does not fit teams that would rather tag resources through a cloud provider policy.
Who is it for?
Terratag fits a team that already manages infrastructure as code and needs to retrofit tags or labels across many existing resources without hand-editing every file, and it does not fit a team whose tagging policy is enforced at the cloud provider level, where the provider's own tag policies do the same job without touching source files.
Can I use it commercially?
Yes, with conditions. MPL-2.0 is a weak copyleft licence: you can use it inside commercial and closed-source software, but if you distribute changes to its own files, you must publish those changes under the same licence.
Is it still maintained?
Yes. The repository last received commits 17 days ago.
What is it written in?
Mainly Go, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 20, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Terratag edits HCL, it does not call the cloud APIs

Terratag is a CLI tool that enables users of Terraform to automatically create and maintain tags across their entire set of AWS, Azure and GCP resources. What it does is operate on the source files rather than on the deployed resources, and that is the whole design decision.

The README is explicit that it applies tags or labels across an entire set of OpenTofu/Terraform files, and it will apply them to any AWS, GCP and Azure resources. The reason given is that maintaining tags across an application is hard, especially when done manually, and that adding tags to existing IaC lets you benefit from cross-resource tag applications you would have wished you had thought of when you first wrote your OpenTofu or Terraform.

So the tool is a source-to-source transformation. It parses your HCL, finds the resources, and rewrites the tag argument. Nothing is deployed, nothing is queried, and the cloud provider is never contacted. The consequence is that the result is reviewable in a pull request like any other code change, which is a real advantage over an imperative script that patches live resources and leaves no trace in your repository.

The project is from env0, the homepage is terratag.io, the licence is MPL-2.0, and the last push was on 2026-10-06 with v0.8.0 released the same day.

The output is a merged tags block plus a locals map

The before-and-after example in the README is the clearest explanation of the mechanism. Before, an aws_s3_bucket resource carries a tags block with a single Name entry, and a google_storage_bucket carries a labels map with one entry. After running Terratag, the resource argument becomes a merge with a local, and a locals block is added holding the tag values.

The AWS resource ends up with:

hcl
resource "aws_s3_bucket" "b" {
  bucket = "my-tf-test-bucket"
  acl    = "private"

  tags = merge( map("Name", "My bucket" ), local.terratag_added_main)
}
locals {
  terratag_added_main = {"env0_environment_id"="dev","env0_project_id"="clientA"}
}

And the GCP resource gets the same treatment on its labels argument, merging the existing map with the same local. The local name, terratag_added_main, is the same across files, which is what makes the tag set consistent across resources.

The merge function is the detail that makes this non-destructive by default. The original Name tag is preserved and the new keys are added. What the notes say is that resources already having the exact same tag as the one being appended will be overridden, and the --keep-existing-tags flag changes that, preserving existing tags when merging instead of letting new ones win.

Originals are kept as .bak files and the output lands beside them

Terratag does not overwrite your files in place. The example output listing shows four files where there were two:

code
|- aws.terratag.tf
|- gcp.terratag.tf
|- aws.tf.bak
|- gcp.tf.bak

The original file is renamed with a .bak suffix and the tagged version is written as a new file with a .terratag.tf suffix. For OpenTofu files the pattern is .terratag.tofu. That means the operation is reversible by hand and the diff is reviewable, but it also means your working tree now has two files per resource file until you clean up the backups.

Two flags control this. The -rename flag, defaulting to false, keeps the original filename instead of replacing a file named <basename>.tf or .tofu with <basename>.terratag.tf or <basename>.terratag.tofu. The -skipTerratagFiles flag, also defaulting to false, controls whether files matching *.terratag.tf and *.terratag.tofu are skipped when processing, which matters because you will run Terratag more than once for the same directory and the second run would otherwise process its own output.

That second-run behaviour is worth understanding before you automate the tool, since the interaction between the two flags determines whether a repeated run is idempotent or accumulates layers.

Filters are regular expressions, and skip is applied after filter

The optional flags give you control over which resources are touched. The -filter flag takes a regular expression, defaults to.* which matches everything, and limits tagging to the resource types it matches. The -skip flag also takes a regular expression, defaults to empty meaning no exclusion, and excludes the matched resource types from tagging, and it is applied after -filter.

The README gives three combinations that cover most cases. To include only S3 and DynamoDB resources:

bash
terratag -tags='{"env":"prod"}' -filter='aws_s3_bucket|aws_dynamodb_table'

To tag everything except IAM resources:

bash
terratag -tags='{"env":"prod"}' -skip='^aws_iam_'

And to combine them, tagging all aws_ prefixed resources while excluding IAM:

bash
terratag -tags='{"env":"prod"}' -filter='^aws_' -skip='^aws_iam_'

The order matters and the README states it, which is the kind of detail that saves an afternoon. Because skip runs after filter, a resource excluded by skip stays excluded even when filter matched it. The IAM case is the obvious example to reach for.

Note the quoting in the examples. The tags value is passed as JSON with escaped quotes, or in the alternate comma form -tags="environment_id=prod,some-tag=value", so a shell argument containing quotes needs care.

The same options exist as environment variables, with flags winning

Every setting can also come from the environment, and the README is explicit about precedence: CLI flags have a precedence over environment variables. The variable names are a direct uppercasing of the flag names:

code
TERRATAG_TAGS
TERRATAG_DIR
TERRATAG_SKIPTERRATAGFILES
TERRATAG_FILTER
TERRATAG_SKIP
TERRATAG_VERBOSE
TERRATAG_RENAME
TERRATAG_TYPE
TERRATAG_DEFAULT_TO_TERRAFORM
TERRATAG_KEEP_EXISTING_TAGS

This is what makes the tool usable in CI, where the tag values differ per environment or per pull request and hardcoding them in a script is awkward. The dependency list explains the implementation: viper 1.21.0 handles the configuration and environment binding, and spf13/pflag handles the flag parsing.

Two flags deserve a note because they change behaviour in ways that are easy to miss. The -type flag accepts terraform, terragrunt or terragrunt-run-all and defaults to terraform, and the same default covers OpenTofu. The -default-to-terraform flag exists because by default Terratag uses OpenTofu if it is installed, and this flag makes it use Terraform even when OpenTofu is present. If you have both installed, the default is not the one a Terraform-only team might expect.

The -dir flag defaults to the current directory and sets the folder to tag, covering both .tf and .tofu files.

Terragrunt support works from the cache, and explicit stacks are not covered

Terratag can be used with Terragrunt in two ways. Use -type=terragrunt for a standard unit, and -type=terragrunt-run-all for implicit stacks. The -type flag documentation explains that with terragrunt it tags the files under the .terragrunt-cache folder, and warns that if Terragrunt does not create a .terragrunt-cache folder you should use the default type or omit the flag.

The important limitation is stated directly: explicit stacks are not explicitly supported. The workaround is to run Terratag on each unit individually with -type=terragrunt and -dir=<unit_path>. For a generated stack similar to an implicit stack, you can use -type=terragrunt-run-all from the generated stack directory, which is .terragrunt-stack by default, and you need to initialise the units in the stack first, either by running terragrunt stack run init or by running terragrunt init in each unit beforehand.

That initialisation step is not optional. The same applies to the main workflow, since Terratag needs the provider schema and child modules, which is why the usage steps start with tofu init or terraform init in the target directory.

The supported providers are listed in the notes: aws, google, azurerm, azurestack and azapi. The repository has an azapi/ directory alongside the cli/, cmd/ and internal/ packages, and the go.mod requires the HCL v2 library from HashiCorp, go-cty for value types, and hashicorp/go-version for version handling, with a doublestar library for glob patterns and a Levenshtein distance library that comes in transitively.

Editorial conclusion

Terratag fits a team that already manages infrastructure as code and needs to retrofit tags or labels across many existing resources without hand-editing every file, and it does not fit a team whose tagging policy is enforced at the cloud provider level, where the provider's own tag policies do the same job without touching source files. Before running it, initialise your modules so provider schema and child modules are available, review the generated .terratag.tf files in a diff before committing them, and decide up front whether to use --keep-existing-tags, since by default new tags override existing ones.

Frequently asked questions

How do I install Terratag?

The README documents a Homebrew install with brew install env0/terratag/terratag, or you can download the latest release binary from the GitHub releases page. The tool requires OpenTofu 1.x or Terraform 0.12 through 1.x.

Does Terratag change my existing Terraform files?

It writes a new file alongside the original: the original is kept with a .bak suffix and the tagged version is written as <basename>.terratag.tf or <basename>.terratag.tofu. The -rename flag keeps the original filename instead.

How do I tag only some resources in Terratag?

Use the -filter flag with a regular expression to include resource types, and -skip to exclude them. Skip is applied after filter, so terratag -tags='{"env":"prod"}' -filter='^aws_' -skip='^aws_iam_' tags AWS resources but leaves IAM out.

Can Terratag run in CI with environment variables?

Yes. Options can be set through TERRATAG_TAGS, TERRATAG_DIR, TERRATAG_SKIPTERRATAGFILES, TERRATAG_FILTER, TERRATAG_SKIP, TERRATAG_VERBOSE, TERRATAG_RENAME, TERRATAG_TYPE, TERRATAG_DEFAULT_TO_TERRAFORM and TERRATAG_KEEP_EXISTING_TAGS, with CLI flags taking precedence over environment variables.

Official sources

  1. env0/terratag on GitHub
  2. License: MPL-2.0
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/env0-terratag.svg)](https://hysenlabs.com/projects/env0-terratag)

Was this useful?