Kokil Thapa - Professional Web Developer in Nepal
Freelancer Web Developer in Nepal with 15+ Years of Experience

Kokil Thapa is an experienced full-stack web developer focused on building fast, secure, and scalable web applications. He helps businesses and individuals create SEO-friendly, user-focused digital platforms designed for long-term growth.

Terraform Import: Bring Existing Resources Under Control

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.

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.

Terraform Import: Bring Existing Resources Under ControlManual InfraConsole, CLI, panelsImport StepLink ID to stateManaged IaCGit, plan, applyWhat Import Does NOT DoDoes not write HCL for youDoes not change live resource settingsDoes not replace backups or runbooksYou still align config until plan is clean
Terraform import connects live infrastructure to state—it does not auto-generate your full configuration.

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.

Terraform Import Workflow1. Write HCL2. Import3. Plan4. Fix Drift5. CleanPlanExample CLI Import (AWS EC2)terraform import aws_instance.app i-0abc123def456789terraform planUpdate HCL until plan shows no changesNever apply a destructive plan during import
Standard Terraform import workflow: write config, import by ID, plan, fix drift, repeat until the plan is empty.
  1. Initialize the working directory: terraform init.
  2. Write the target resource block with a stable local name.
  3. Run import with the correct provider ID string.
  4. Run terraform plan and read every proposed change.
  5. Update HCL—or use lifecycle rules—until plan output is empty.
  6. 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.

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.

Criteriaterraform import CLIImport block (HCL)
Preview in planNo—runs immediatelyYes—shows import in plan output
Version controlCommand history only unless scriptedImport intent lives in Git
CI/CD friendlyNeeds wrapper scriptsWorks with standard plan/apply pipelines
Config generationManual HCL writing-generate-config-out can draft blocks
Learning curveLower for one-off importsBetter for team review at scale
OpenTofu supportSupportedSupported—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.

CLI Import vs Import BlockCLI ImportImmediate, one resourceGood for quick fixesHarder to audit in CIImport BlockPlan-first, reviewableGit-tracked intentFits PR-based IaCShared Next StepRun plan until zero diffRemove import block after success
CLI import and import blocks differ in review workflow—both require a clean plan before you treat resources as fully managed.

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.

Terraform Import PitfallsDestructive PlanFix HCL, never blind applyWrong ID FormatCheck provider docsSkipped DepsImport VPC before EC2Local StateUse remote backend firstTag MismatchCopy live tags to HCLNo CI GatePlan-only until cleanSafe Exit: Empty Plan + Locked Remote StateThen enable apply in GitHub Actions or GitLab CI
Avoid destructive applies, wrong IDs, and missing dependencies when you use Terraform import to bring existing resources under control.

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 plan after 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

Terraform import links a live cloud resource ID to an address in your Terraform state file. It does not create, modify, or delete the real infrastructure. You write a matching resource block in HCL, run terraform import ADDRESS ID or declare an import block, then run terraform plan until the diff is empty. Only after a clean plan is Terraform truly managing the asset. Import is the adoption step that connects manually built servers, databases, load balancers, or VPS boxes to version-controlled infrastructure without tearing production down.

Most teams hit this after a successful launch when production runs on manually created EC2 instances, RDS databases, or VPS boxes. Rebuilding with a greenfield terraform apply risks downtime and SLA violations. Import tells Terraform the resource already exists and should be tracked. You keep servers running while gaining repeatable plans, peer review on infra changes, and the same infrastructure-as-code workflow used on greenfield projects. Common triggers include audit findings, team growth, multi-environment drift, and inherited cloud accounts from mergers. 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.

Preparation prevents the worst outcome: a plan that wants to destroy production because your HCL does not match reality. Inventory every asset with provider IDs, region, tags, and attached dependencies. Pin provider versions and configure a remote backend with locking before multiple engineers touch the project. Write minimal matching resource blocks with the correct resource type and local name, omitting computed attributes initially. Use separate state files or Terraform workspaces so a prod import never touches dev state. Commit a versions.tf block locking Terraform to at least 1.5.0 and your provider, for example AWS at roughly version 5.x, plus an S3 backend with DynamoDB locking.

Initialize the working directory with terraform init. Write the target resource block with a stable local name. Run import using the correct provider ID string via CLI or declare an import block in HCL. Run terraform plan and read every proposed change carefully. Update HCL or add lifecycle rules until plan output is empty. Commit code and backend config to Git, and wire CI only after a clean plan. For import blocks in Terraform 1.5 or later, run terraform plan to preview the import, then terraform apply to execute it. You can draft starter configuration with terraform plan -generate-config-out=generated.tf, but review that output carefully because auto-generated HCL is a draft, not production-ready code.

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.

Both paths land in the same state but differ in workflow fit and reviewability. The CLI command binds one resource immediately without showing the import inside a plan preview, which suits quick one-off fixes like a single EC2 box. Import blocks declared in HCL let you preview imports inside a normal terraform plan, store import intent in Git, and integrate cleanly with standard plan and apply CI pipelines. Import blocks also work with -generate-config-out to draft resource blocks. For a production AWS account with twenty resources, import blocks plus a pull request beat ad-hoc shell history. OpenTofu supports both approaches; pin versions and test regardless of which binary you use.

Import succeeds when state contains the resource; adoption succeeds when terraform plan shows no changes. Read the plan like an incident report. A proposed destroy-and-recreate is a stop sign—never apply it blindly. Common causes include wrong AMI values, missing tags in HCL, or attributes that cannot change in place. Use lifecycle meta-arguments such as ignore_changes to freeze noisy fields like ami, user_data, or root_block_device volume size, but document every ignored field so the next engineer does not inherit silent drift. Once individual resources plan clean, refactor into modules and variables. Scan results with Checkov before granting CI apply permissions.

A small-business migration of 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.

State points at the wrong object, so the next plan compares your HCL against a mismatched resource and often shows massive unexpected changes. If you discover the error before applying, remove the resource from state with terraform state rm and re-import the correct ID. Never delete the live cloud resource unless you intend to. Wrong-ID imports are a common stall point during adoption because console names and Terraform local names diverge. Document original console names alongside imported addresses in your runbook so the team can verify IDs against the inventory captured during preparation.

The CLI imports one address per command, but you can script a loop or declare multiple import blocks in one module and apply them together. Bulk tools like Terraformer generate HCL and state together, though you still review and refactor output before production use. For large adoptions, split work into modules—network, compute, data—and import layer by layer rather than attempting everything in one pull request. 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.

Follow dependency order to avoid import errors and hidden coupling. Import networks before compute, IAM roles before instances that assume them, and subnets before ENI attachments. For bulk adoption on AWS, codify VPCs, subnets, and security groups before EC2 instances, RDS databases, and attached storage. On VPS providers like Hetzner or DigitalOcean, adopt firewalls, volumes, and DNS records before the server resource that depends on them. Layering imports across Terraform modules—network first, then compute, then data—keeps each pull request reviewable and reduces the chance that a missing upstream dependency triggers a destructive plan later.

Move state to a remote backend with locking before multiple people touch the adoption project, not after drift cleanup is finished. Remote state with DynamoDB or equivalent locking prevents two engineers from corrupting the same import work. Pair import with a solid backend early so plan and apply operations are consistent across laptops and CI runners. Commit backend configuration alongside provider pins in versions.tf. An S3 backend with encryption and a DynamoDB lock table is the pattern described in the article for production AWS workloads. Skipping remote state during import is risky once more than one person participates in drift fixes.

Treat import pull requests differently from feature PRs. Phase one enables terraform plan on every push and blocks apply until imports reach a zero-diff plan. The runner executes plan, uploads artifacts, and waits for human approval—patterns from Terraform CI/CD with GitHub Actions apply equally on GitLab CI. Phase two allows controlled apply on main only after the first clean plan. Phase three adds policy guardrails such as Sentinel or OPA if you use Terraform Cloud. Enable apply only after plans are consistently empty and Checkov scans pass. Mid-migration is the wrong time to switch engines or compare Terraform against Pulumi or 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.

When using import blocks in Terraform 1.5 or later, you can run terraform plan -generate-config-out=generated.tf to produce starter resource blocks from live infrastructure. This speeds up the hardest part of adoption—writing HCL that matches reality—but the output is a draft, not production-ready configuration. Review generated files carefully for hard-coded IDs, missing tags, and attributes that should use variables or lifecycle ignore_changes instead. Use generated config as a starting point, then refactor into modules, variables, locals, and outputs once individual resources plan clean. Never commit generated.tf blindly or wire CI apply permissions based on unreviewed auto output.

Share this article

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.

Quick Contact Options
Choose how you want to connect me: