Import Resources into Terraform

Terraform import brings existing infrastructure under state file management. Learn what import options you have.

Why Import Resources into Terraform

Importing resources into Terraform is the process of bringing existing, manually provisioned infrastructure under Terraform's management. This is a crucial step when you have infrastructure that was created outside of your Terraform configuration, such as resources provisioned directly through a cloud provider's console. The import operation doesn't create the resource; instead, it matches an existing cloud resource to a corresponding resource block in your Terraform code and records its state in your Terraform state file. Once imported, that resource is fully managed by Terraform, allowing you to use terraform plan and terraform apply to track and manage its configuration, ensuring your state file accurately reflects your live infrastructure.

There are two ways to import resources: The Terraform import block and the terraform import command.

Terraform Import Command vs. The Import Block

Before Terraform 1.5, importing existing infrastructure was exclusively done via the terraform import command. This method is an imperative operation, meaning you directly instruct Terraform to perform an action at a specific time. You would run the command, provide the resource address and the external resource ID, and Terraform would then add that resource to its state file. The crucial next step was for the user to manually write the corresponding Terraform configuration block (HCL) that matched the imported resource's actual state. This process was often iterative, involving terraform plan to identify configuration drift after the import.

With Terraform 1.5, HashiCorp introduced the import block, a declarative alternative integrated directly into the Terraform configuration language (HCL). This block allows you to define which resources should be imported as part of your normal terraform plan and apply workflow. Instead of a separate command, you specify an import block in your .tf files, referencing the target resource in your configuration (to = <resource_address>) and providing the id of the existing resource you want to import. When you run terraform plan and terraform apply with an import block present, Terraform performs the import operation, and importantly, it can also generate a starting configuration for the imported resource (using terraform plan -generate-config-out), streamlining the post-import cleanup.

The key differences boil down to automation and safety:

  • terraform import (command): This is a good choice for one-off, interactive imports where you might be bringing a single resource under management. It's less ideal for large-scale migrations or CI/CD pipelines because it requires manual post-import configuration writing and is an external, imperative step.
  • import block: This is the recommended approach for most modern use cases, especially for automating imports, large-scale migrations, and integrating imports into CI/CD pipelines. Its declarative nature allows the import process to be part of the regular plan/apply cycle, providing better visibility and control, including the ability to import multiple resources simultaneously using for_each, and the helpful configuration generation feature. While the command still exists and has its place for quick fixes, the import block offers a more robust and integrated solution for managing existing infrastructure.

Import Block Examples

This example uses the for_each argument within an import block to import multiple S3 buckets. This shows how to import resources into specific module instances.

Importing Resources Across Module Instances

import {
  for_each = { for b in local.buckets : "${b.group}.${b.key}" => b }
  id       = each.value.id
  to       = module.group.aws_s3_bucket.this
}

Importing Multiple Resources with for_each

locals {
  buckets = {
    "staging" = "staging-bucket"
    "uat"     = "uat-bucket"
    "prod"    = "production-bucket"
  }
}

import {
  for_each = local.buckets
  to       = aws_s3_bucket.app_data[each.key]
  id       = each.value
}

resource "aws_s3_bucket" "app_data" {
  for_each = local.buckets
  # ... bucket configuration ...
}

Terraform Import Command Basics

The import command allows you to link a remote, pre-existing resource to a corresponding resource block in your Terraform configuration and record it in the Terraform state file. This doesn't modify the actual infrastructure; it merely updates Terraform's state so that future terraform plan and terraform apply operations can manage that resource alongside others defined in your code. The basic syntax involves specifying the Terraform resource address (e.g., aws_instance.example) and the unique ID of the existing remote resource (e.g., i-1234567899abcdef0 for an AWS EC2 instance).

Why Terraform Import is Used

  • Manage existing resources: Integrate infrastructure created outside of Terraform (e.g., manually via the cloud console, using scripts, or other IaC tools) into your Terraform workflow.
  • Avoid recreation: Prevent the need to delete and recreate existing resources, which can be disruptive and time-consuming.
  • Consistency and control: Manage all your infrastructure through Terraform, ensuring a consistent and controlled environment.
  • Migration: Gradually adopt Terraform for existing resources, minimizing disruption.
  • Disaster Recovery: If your Terraform state file is lost or corrupted, you can use terraform import to rebuild it.

Examples of Using the Command

Here are some basic examples of using the terraform import command:

Importing an AWS EC2 Instance

This command imports an EC2 instance with the ID i-abcd1234 and associates it with the resource block aws_instance.example.

terraform import aws_instance.example i-abcd1234

Importing an AWS S3 Bucket

This command imports an existing S3 bucket named my-existing-bucket and associates it with the resource block aws_s3_bucket.example in your Terraform configuration. You'll need to have a corresponding resource block defined in your .tf file.

terraform import aws_s3_bucket.example my-existing-bucket

Importing an Azure Virtual Machine

Azure resource IDs are typically full paths.

terraform import azurerm_virtual_machine.example /subscriptions/xxx-xxx-xxx-xxx-xxx/resourceGroups/myRG/providers/Microsoft.Compute/virtualMachines/myVM

Terraform Import Flags

Here are the most common and useful flags you can use with the terraform import command:

  • -config=path: Specifies the path to the directory containing your Terraform configuration files. By default, Terraform looks in the current working directory.
  • -input=true/false: Determines whether Terraform should ask for interactive input for provider configuration or variable values. Setting it to false is useful for automation and scripting.
  • -lock=false: Disables holding a state lock during the operation. Caution: This is generally not recommended in a collaborative environment as it can lead to state corruption if other commands are run concurrently.
  • -lock-timeout=0s: Sets a duration to retry acquiring a state lock before failing.
  • -no-color: Disables colorized output in the console.
  • -parallelism=n: Limits the number of concurrent operations Terraform performs when walking the graph. The default is typically 10.
  • -provider=provider (Deprecated): Overrides the provider configuration to use when importing an object. It's generally best practice to let Terraform use the provider specified in the configuration for the target resource.
  • -var 'foo=bar': Sets a variable in the Terraform configuration directly from the command line. This flag can be used multiple times for different variables. Variable values are interpreted as literal Terraform expressions.

When using flags, they typically need to appear before the positional arguments (ADDRESS and ID) in the command. For example: terraform import -var 'env=prod' aws_instance.my_instance i-1234567890abcdef0.

Tips and Tricks

Write the resource block first: Before running the terraform import command, define the corresponding resource block in your Terraform configuration file.

Identify the resource ID: You'll need the unique ID of the resource you're importing (e.g., the S3 bucket name, EC2 instance ID, or Azure resource path). Refer to the provider documentation for the correct ID format.

Use terraform plan: After importing, always run terraform plan to see the differences between the imported resource's actual state and your configuration. Adjust your configuration as needed.

Consider using terraform plan -generate-config-out: Terraform can generate the configuration for you, which can be a helpful starting point, especially for complex resources (Terraform 1.5+).

Be aware of dependencies: Import resources in the correct order, considering dependencies between them.

Back up your state file: It's always a good practice to back up your Terraform state file before performing import operations.

Use import blocks for CI/CD: The import block integrates well with CI/CD pipelines.

Remove import blocks: After successfully importing a resource, you can remove the import block from your configuration.

When Not to Use the Import Command

When you can easily recreate the resource: If the resource is simple to recreate from scratch using Terraform, it might be safer and cleaner to do so.

For core infrastructure with high risk of disruption: Importing fundamental resources like networking (VPCs, subnets) or IAM can be risky, as misconfigurations can lead to widespread outages. Consider creating parallel resources and migrating gradually.

When you are unsure about the existing configuration: If you don't fully understand the configuration of the existing resource, importing it can lead to unexpected changes.

Using Terraform Import in a Remote Backend

If Terraform automation and collaboration software, such as Scalr or Terraform Cloud, are used, there is one extra step to consider before doing an import. Since the remote backend is managing the state, the backend must be specified in the code to ensure that the resources are added to the correct state file. The backend code looks like:

terraform {
  backend "remote" {
    hostname = "my-account.scalr.io"
    organization = "<ID of environment>"
    workspaces {
      name = "<workspace-name>"
    }
  }
}

Once the code is added and the import command is executed, the imported resources will be added to the state in the backend.

Verifying the Import in State

After executing the terraform import command, it's important to verify that the resource has been successfully brought under Terraform's management. The primary way to confirm this is by running terraform plan. A successful import will result in a plan that shows "No changes. Your infrastructure matches the configuration." or, if you've imported a resource and then immediately added a new attribute or dependency in your HCL, it might show only the planned changes for those new additions, but not a plan to create or destroy the imported resource itself. If terraform plan shows that the imported resource would be created or destroyed, it indicates the import failed or that the resource block in your configuration does not perfectly match the imported resource.

Terraform Show

Additionally, you can inspect the state file directly or use terraform show to see if the imported resource now appears within Terraform's recorded state.

terraform show

aws_instance.example:
resource "aws_instance" "example" {
    ami                                  = "ami-09bd3f9be7c1b8217"
    arn                                  = "arn:aws:ec2:us-east-1:123456:instance/i-0354906e43123456"
    associate_public_ip_address          = true
    availability_zone                    = "us-east-1d"
    cpu_core_count                       = 1
    ...
    ..
    .

Terraform State List

Running terraform state list is another excellent way to verify that an import was successful. After running terraform import, if the operation was successful, the imported resource's address will appear in the output when you execute terraform state list. This command directly queries the current state file and lists all resources that Terraform is currently managing, providing a quick and clear confirmation that your new resource has been added to the state.

terraform state list

aws_instance.example

Summary

Terraform import is a vital command for bringing existing, unmanaged infrastructure under Terraform's Infrastructure as Code (IaC) control. It prevents the disruptive recreation of resources, enabling seamless migration, consistent management, and enhanced control over your cloud environment. While the traditional terraform import command is an imperative, one-off operation requiring manual HCL configuration, Terraform 1.5 introduced the import block. This declarative alternative integrates imports directly into your HCL, allowing for automated, multi-resource imports, and even configuration generation, making it ideal for CI/CD pipelines and large-scale adoption. When importing, it's crucial to write the resource block first, obtain the correct resource ID, and always run terraform plan afterward to reconcile differences. A strict import order (parent before child) is necessary for resources with dependencies. The import process updates your Terraform state directly, including when using remote backends like Scalr or Terraform Cloud, ensuring centralized and synchronized state management. While powerful, terraform import should be used judiciously, avoiding unnecessary imports for easily recreatable resources or high-risk core infrastructure.