Importing Existing Resources
Infrastructure often exists before the code that manages it: a bucket someone created in the console, a VPC that predates your adoption of OpenTofu/Terraform, or resources you are moving out of a Terralith. Importing writes those resources into state so the unit that should own them stops planning to recreate them.
Importing is an OpenTofu/Terraform operation. What Terragrunt adds is that state is segmented per unit, so the unit you run the import from determines which state file the resource lands in.
Importing a single resource
Section titled āImporting a single resourceāRun import from inside the unit that should own the resource:
cd live/prod/vpcterragrunt import aws_vpc.this vpc-0123456789abcdef0import is one of the OpenTofu shortcuts, so the command above is equivalent to:
terragrunt run -- import aws_vpc.this vpc-0123456789abcdef0The resource address is resolved against the module the unit sources in its terraform block, not against your terragrunt.hcl. If the unit sources a module that wraps the resource in a submodule, address it through that path:
terragrunt import 'module.vpc.aws_vpc.this' vpc-0123456789abcdef0Quote addresses that contain square brackets so your shell does not expand them:
terragrunt import 'aws_subnet.private[0]' subnet-0123456789abcdef0Terragrunt runs the underlying command in the unitās working directory, which is inside .terragrunt-cache rather than the directory you invoked from. Inputs, generated files, and backend configuration are all applied first, so the import sees the same configuration a plan would. Resource IDs are passed through untouched.
Verifying the import
Section titled āVerifying the importāA successful import is one that produces no diff:
terragrunt planIf the plan still wants to create the resource, the address you imported to does not match the address in your configuration. If it wants to change or replace the resource, your configuration and the real resource disagree, and you should reconcile the configuration to match what exists before applying anything.
Importing when the unit has dependencies
Section titled āImporting when the unit has dependenciesāA unit that reads outputs from a dependency needs those outputs to resolve before any command runs, including import. When the dependency has already been applied this is automatic. When it has not, add import to the commands that are allowed to use mocks:
dependency "vpc" { config_path = "../vpc"
mock_outputs = { vpc_id = "vpc-mock" }
mock_outputs_allowed_terraform_commands = ["validate", "plan", "import"]}Without import in that list, Terragrunt errors out while resolving the dependency rather than while importing.
Using import blocks
Section titled āUsing import blocksāThe command above is a one-off: it changes state on the machine that runs it and leaves no record. OpenTofu/Terraform import blocks declare imports in configuration instead, so they show up in a plan, get reviewed, and run the same way in CI.
If the unit sources a local module you control, add the block to the moduleās .tf files. If it sources a remote module you do not control, use a generate block to write the file into the working directory:
terraform { source = "git::git@github.com:acme/modules.git//vpc?ref=v1.2.3"}
generate "imports" { path = "imports.tf" if_exists = "overwrite_terragrunt" contents = <<-EOF import { to = aws_vpc.this id = "vpc-0123456789abcdef0" } EOF}Run terragrunt plan to preview what will be imported, then terragrunt apply to perform it.
An import block is only active while the address in to is absent from state, so once the import has landed the block goes inert and later runs ignore it. Terragrunt regenerating the file into each new working directory does not re-import anything. Removing the generate block afterwards is therefore optional: drop it to keep a hardcoded resource ID out of your configuration, or keep it as a record of where the resource came from.
Importing many resources
Section titled āImporting many resourcesāFor anything beyond a handful of resources, generate the imports rather than typing them. Both of these help:
-
-generate-config-outwrites configuration for resources named inimportblocks that have no matching resource in your code, which you then edit into shape. Give it an absolute path so the file lands next to yourterragrunt.hcl:Terminal window terragrunt run -- plan -generate-config-out=$PWD/generated.tfOpenTofu resolves this path against its own working directory, which is inside
.terragrunt-cache. A bare-generate-config-out=generated.tftherefore writes into scratch space that you can delete at any time.$PWDis expanded by your shell before Terragrunt runs, so it points at the unit directory you invoked from. -
Your cloud providerās CLI can list resource IDs, which you can turn into
importblocks with a short script.
Import into small units rather than one large one. A unit holding a hundred imported resources has the same problems as the Terralith you are trying to escape, and splitting state after the fact is more work than importing into the right shape to begin with.