
September 10, 2026
11 min read
By Kokil Thapa | Last reviewed: September 2026
You built servers, databases, and load balancers by hand. Now the team wants version-controlled infrastructure. Terraform import: bring existing resources under control is how you adopt live assets without tearing them down and rebuilding. The import step only links real-world IDs to Terraform state. You still write matching HCL, fix attribute drift, and wire up remote backends before CI can manage changes safely.
terraform import ADDRESS ID or use an import block, then run terraform plan until the diff is empty.Why Would You Use Terraform Import to Bring Existing Resources Under Control?
Most teams hit this wall after a successful launch. Production runs on manually created EC2 instances, RDS databases, or VPS boxes. Nobody wants downtime from a greenfield Terraform apply that tries to recreate everything.
Import solves the adoption problem. It tells Terraform, “this resource already exists—track it.” From there you gain repeatable plans, peer review on infra changes, and the same infrastructure as code with Terraform workflow you would use on a greenfield project.
On shared EC2 setups I maintain with Deployer 7 and GitLab CI, import is often the first step before codifying security groups, Elastic IPs, or S3 buckets that predated IaC. The servers keep running. Only the management layer changes.
Common triggers include audit findings, team growth, multi-environment drift, and mergers where inherited cloud accounts lack documentation. Import is also cheaper than parallel rebuilds when SLAs forbid maintenance windows.
Pair import with a solid backend before you scale. Remote state with locking prevents two engineers from corrupting the same adoption project. See Terraform state management and remote backends and manage Terraform state safely for the setup pattern.
How Do You Prepare Before Running Terraform Import?
Preparation prevents the worst outcome: a plan that wants to destroy production because your HCL does not match reality.
Inventory and document live resources
List every asset you intend to adopt. Capture provider IDs, region, tags, attached dependencies, and who owns DNS or billing ties. For AWS that means instance IDs, subnet IDs, security group IDs, and ARNs. For a VPS provider, note the server UUID and attached volume IDs.
Pin provider versions and configure backend
Lock providers before import. Drift in provider schemas causes noisy plans later. Follow Terraform provider version pinning and commit a versions.tf block:
terraform {
required_version = ">= 1.5.0"
backend "s3" {
bucket = "company-terraform-state"
key = "prod/network/terraform.tfstate"
region = "ap-southeast-1"
dynamodb_table = "terraform-locks"
encrypt = true
}
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
} Write minimal matching resource blocks
Import requires a destination address in configuration. Start with the smallest valid block. Omit computed attributes. Match the resource type and name exactly.
resource "aws_instance" "app" {
# Fill required arguments after first plan reveals drift.
ami = "ami-0abcdef1234567890"
instance_type = "t3.small"
subnet_id = "subnet-0abc123"
} Use separate state files or Terraform workspaces and environments so a prod import never touches dev state.
What Is the Step-by-Step Terraform Import Workflow?
Two official paths exist: the CLI command and declarative import blocks. Both achieve the same goal—bind an external ID to a resource address in state.
- Initialize the working directory:
terraform init. - Write the target
resourceblock with a stable local name. - Run import with the correct provider ID string.
- Run
terraform planand read every proposed change. - Update HCL—or use lifecycle rules—until plan output is empty.
- Commit code and state backend config to Git; wire CI only after a clean plan.
CLI import command
The classic form binds one resource at a time. Official syntax is documented in the Terraform CLI import reference:
terraform import aws_instance.app i-0abc123def456789
terraform import aws_s3_bucket.logs my-company-logs-prod
terraform import aws_db_instance.primary mydb-prod Module addresses include the module path:
terraform import module.network.aws_subnet.public subnet-0abc123 For resources with composite IDs, use the format the provider expects. Some need subnet-id/security-group-id or similar joined strings. Check the provider docs for that resource type.
Declarative import blocks (Terraform 1.5+)
Import blocks let you declare imports in HCL and preview them inside a normal plan. HashiCorp documents this in the Terraform import block specification.
import {
to = aws_instance.app
id = "i-0abc123def456789"
}
resource "aws_instance" "app" {
ami = "ami-0abcdef1234567890"
instance_type = "t3.small"
subnet_id = "subnet-0abc123"
} Run terraform plan. If the plan looks correct, run terraform apply to execute the import. You can generate starter configuration with:
terraform plan -generate-config-out=generated.tf Review generated files carefully. Auto output is a draft, not production-ready HCL.
Import related resources in dependency order
Networks before compute. IAM roles before instances that assume them. Subnets before ENI attachments. Skipping order produces import errors or hidden dependencies that break later applies.
For bulk adoption, split work into Terraform modules—network, compute, data—and import layer by layer.
How Do Terraform Import CLI and Import Blocks Compare?
Both paths land in the same state. The difference is workflow fit, reviewability, and CI integration.
| Criteria | terraform import CLI | Import block (HCL) |
|---|---|---|
| Preview in plan | No—runs immediately | Yes—shows import in plan output |
| Version control | Command history only unless scripted | Import intent lives in Git |
| CI/CD friendly | Needs wrapper scripts | Works with standard plan/apply pipelines |
| Config generation | Manual HCL writing | -generate-config-out can draft blocks |
| Learning curve | Lower for one-off imports | Better for team review at scale |
| OpenTofu support | Supported | Supported—see OpenTofu fork notes |
For a single EC2 box on a client VPS, CLI import is fast. For a production AWS account with twenty resources, import blocks plus generated config and a pull request beat ad-hoc shell history.
How Do You Fix Drift After Terraform Import?
Import succeeds when state contains the resource. Adoption succeeds when terraform plan shows no changes. That gap is where most projects stall.
Read the plan like an incident report
A plan that proposes -/+ destroy and recreate is a stop sign. Never apply it blindly. Common causes include wrong ami, missing lifecycle { ignore_changes = [...] }, or tags that exist in AWS but not in HCL.
Use JSON formatter tools to pretty-print plan JSON in CI logs when human-readable output hides nested attribute diffs.
Use lifecycle meta-arguments carefully
Some attributes cannot be changed in place. Others should stay manual during a phased adoption. The Terraform lifecycle meta-argument helps freeze noisy fields:
resource "aws_instance" "app" {
ami = "ami-0abcdef1234567890"
instance_type = "t3.small"
subnet_id = "subnet-0abc123"
lifecycle {
ignore_changes = [
ami,
user_data,
root_block_device[0].volume_size,
]
}
} Document every ignored field. Otherwise the next engineer inherits silent drift.
Refactor into modules and variables
Once individual resources plan clean, extract repeated patterns. Move CIDR blocks and instance sizes into Terraform variables, locals, and outputs. Use for_each vs count when importing fleets of similar servers.
Scan the result with Checkov for Terraform misconfigurations before you grant CI apply permissions.
How Do You Wire Terraform Import Into CI/CD and Team Process?
Import is a one-time or phased migration task. Ongoing management belongs in CI. Treat import PRs differently from feature PRs.
Phase 1: plan-only pipeline
Enable terraform plan on every push. Block apply until imports reach a zero-diff plan. Patterns from Terraform CI/CD with GitHub Actions apply equally on GitLab CI—the runner executes plan, uploads artifacts, and waits for human approval.
Phase 2: controlled apply
After the first clean plan on main, allow applies for incremental changes only. Use Terragrunt when multiple accounts or regions share boilerplate.
Phase 3: policy and cost guardrails
Add Sentinel or OPA policies if you use Terraform Cloud. Compare against Terraform vs Pulumi vs OpenTofu only after your import program stabilizes—mid-migration is the wrong time to switch engines.
For VPS-heavy workloads, Terraform for VPS provisioning pairs well with import when DNS, firewalls, and volumes were created manually on Hetzner, DigitalOcean, or similar providers.
If you prefer Ansible for bootstrapping but Terraform for cloud primitives, read Terraform vs Ansible before you import OS-level config that Ansible already manages.
Operational handoff
Document imported addresses, original console names, and runbook links. On production Linux hosts, align server roles with your existing Linux system administration practices—Terraform owns the cloud object, not every file inside the VM.
Sister legal-tech sites on shared EC2—deployed with Deployer 7—benefit from importing security groups and EIPs before codifying autoscaling. Similar patterns appear in the Adventure Third Pole Trek booking platform stack where Laravel, queues, and infra must stay aligned.
Budget time for post-import support. A typical small-business migration—five to fifteen resources—often runs Rs 80,000–150,000 (~USD 600–1,100) when an engineer handles import, drift cleanup, backend setup, and CI wiring. Larger multi-account AWS adoptions cost more and should be phased.
Key Takeaways
- Terraform import links live resource IDs to state—it does not replace writing and tuning HCL.
- Always run
terraform planafter import and stop if the plan proposes destroy/recreate. - Prefer import blocks in Git-backed workflows; use CLI import for quick one-off fixes.
- Import dependencies in order: network and IAM before compute and attached storage.
- Move state to a remote backend with locking before multiple people touch the adoption project.
- Enable CI apply only after plans are consistently empty and Checkov scans pass.
People Also Ask
Does Terraform import create the resource in the cloud?
No. Import only updates Terraform state to reference an object that already exists. The cloud resource is unchanged during import. Configuration changes happen only when you later run terraform apply with a non-empty plan.
Can you import multiple resources at once?
The CLI imports one address per command. You can script a loop or declare multiple import blocks in one module. Bulk tools like Terraformer generate HCL and state together, but you still review and refactor output before production use.
What happens if you import the wrong ID?
State points at the wrong object. The next plan compares your HCL against that mismatched resource, often showing massive unexpected changes. Fix by removing the resource from state with terraform state rm and re-importing the correct ID—never delete the live resource unless you intend to.
Is Terraform import supported on OpenTofu?
Yes. OpenTofu maintains compatibility with import blocks and the CLI import command. Pin versions and test plans in a sandbox regardless of which binary you use.
Start Adopting Live Infrastructure With Terraform Import
Terraform import: bring existing resources under control is the lowest-risk on-ramp to IaC when production already runs. Write minimal HCL, import by ID, plan until the diff is empty, then promote the workflow into CI with remote state and policy scans. Skip any step and you risk a destructive apply on assets that predated Terraform.
If you want help importing EC2, RDS, S3, or VPS resources without downtime, review our support and maintenance services or contact us for a phased adoption plan. For self-study, the HashiCorp state import tutorial walks through a full example end to end.
Frequently Asked Questions
0 Comments
Leave a comment
Your email is not published. Comments appear once they have been read. Sign in to have your details filled in.

