This page covers modules where the definition owns the Terraform source. If you’re importing resources into your own Terraform code running through an
rvn-stack module, see Import into a Terraform Stack — it has a simpler path using import blocks.How it works
- Applying a project config file that adds a stack-backed module creates the instance, its managed state backend workspace, and starts a change pipeline run that stops at the approval gate.
- That run’s plan enumerates exactly which resource addresses the module manages, so you know what to import. You cancel the run instead of approving it.
ravion terraform shellwires your localterraform/tofuto the managed workspace, soterraform importwrites directly into the module’s state. Import only needs stub resource blocks locally — not the module’s real Terraform source.- Applying the config again plans against the imported state. When your module inputs match the real infrastructure, the plan shows no creates — only benign in-place updates such as tags.
Prerequisites
- The Ravion CLI installed and authenticated — verify with
ravion whoami - Local credentials for the provider that owns the resources — for AWS,
aws sts get-caller-identitymust return the same account that’s connected to Ravion (compare withravion aws account list --json) - OpenTofu (or Terraform, matching the module’s tool) installed locally
Use an agent
The import flow is mechanical but detail-heavy: reading resource addresses from the plan, inspecting real resource settings, matching module inputs, mapping addresses to import IDs. A coding agent with access to your provider’s CLI handles it well. The steps are the manual walkthrough below — this prompt points your agent at it and sets the guardrails:Create the module, import existing infrastructure into it, and verify a clean plan.
Manual walkthrough
The example below imports an existing AWS RDS PostgreSQL instance into anrvn-rds module. The same flow applies to any module and any provider — only the resource inspection commands and import IDs change.
1
Add the module to your project config and apply
Pull your project config if you don’t already manage it in source control:Add the module instance. The inputs that identify the existing infrastructure — here the DB instance identifier (Preview, then apply — without The apply creates the module, its managed state workspace, and starts a change run that stops at the approval gate. Nothing is provisioned until a plan is approved.
name), engine, region, and account — must match it exactly, since many are immutable after the first apply. Best-guess the rest; you’ll refine them after seeing the plan. Check the schema with ravion module schema rvn-rds.Since the existing database lives in a VPC that Ravion doesn’t manage, set network: null and pass the VPC and subnets directly. (If the VPC is already an rvn-aws-network module, reference it instead: network: {moduleGivenIdRef: <module-given-id>}.)ravion.yaml
--autoapprove:Prefer managing modules through the config file. For a one-off without config as code,
ravion module create --initial-stack-run PLAN gets you to the same place.2
Read the resource addresses from the plan
The run that just started tells you exactly which resource addresses the module manages:Each CREATE row is a resource to import. Cancel the run — you’re importing instead of letting it create anything, and its plan becomes stale the moment you do:
Example output
3
Match module inputs to the real resource
Inspect the existing resource and record every setting the module’s inputs cover:For RDS,
describe-db-instances covers most inputs in one call: engine and version, instance class, allocated storage, storage type, Multi-AZ, backup retention, Performance Insights, CloudWatch log exports, deletion protection, and the attached subnet group, parameter group, and security groups.Update the module’s inputs in the config file so they match reality — the closer the match, the cleaner the post-import plan. If the real resource has a setting the module’s inputs can’t express, note it and decide whether you can live without it before continuing. Don’t apply yet; the apply after importing doubles as your verification run.4
Write a stub config for the import
terraform import requires each address to exist in configuration, but it doesn’t validate resource arguments — so empty stub blocks are enough. You don’t need the module’s real Terraform source. In a scratch directory, write one stub per address from the plan, mirroring the address shapes exactly:- An indexed address like
aws_db_parameter_group.this[0]needscount = 1on its stub. - A keyed address like
...egress_rule.this["0"]needs a matchingfor_each. - Addresses under
module.security_group[0]need a stub child module with the same name.
main.tf
security_group/main.tf
If you want to read the module’s real source — for import ID formats or to understand conditional resources — the bottom of
ravion module schema <type> links it. For custom module definitions, the repo, ref, and base_path are in the stack config from ravion module version get <module-version-id> --json. Viewing it is enough; there’s no need to clone it.5
Import into the managed workspace
From the stub config directory, get the workspace name from the stack, then initialize and import. Import IDs vary by resource type — the DB instance, parameter group, and subnet group import by name, while the security group and its egress rule import by AWS-assigned ID (
ravion terraform shell wires TF_CLOUD_* and token env vars so tofu talks directly to Ravion’s state backend:sg-..., sgr-...). Each provider’s docs list the format in the resource’s Import section. Single-quote addresses containing [ or " so the shell passes them through intact. Export whatever credentials and settings the provider needs (here, AWS credentials and the region).6
Apply again and verify the plan
Apply the config file with your refined inputs — the run it starts is your verification plan:If the config file didn’t change since the first apply, start the run manually instead:Inspect the plan:A successful import shows no CREATE rows. Expect:If the plan shows CREATE, DELETE, or REPLACE, your module inputs don’t match the real infrastructure. Cancel the run, fix the inputs in the config file, apply, and re-check.When the plan looks right, approve it in the dashboard or via the CLI:After the apply succeeds, the resource is fully managed by the module — future config file changes flow through the normal change pipeline.
NO_OPfor resources whose inputs match exactly.- An in-place
UPDATEon the main resource for tags — Ravion merges standard ownership tags (Owner,ProjectGivenId,EnvironmentGivenId,ModuleGivenId,ModuleId) on top of yours. This is expected and safe.
Tips
- One module instance per pass: a module usually manages several Terraform resources — an RDS module also manages the parameter group, DB subnet group, and security group — but unrelated infrastructure belongs in separate module instances. Repeat the flow for each.
- Immutable inputs: inputs marked
immutablein the schema (like the DB instance identifier, engine, and region) can’t change after the first apply, so get them right in the config file before importing. - Conditional resources: some resources only exist when an input enables them. In
rvn-rds, the parameter group and security group exist becauseparameter_group_creation_enabledandsecurity_group_creation_enableddefault to true — that’s why their addresses are indexed with[0]. Match the inputs to reality: if the real database uses an existing security group, disable creation, pass its ID instead, and skip those imports. - Data sources (like
data.aws_vpc.thisanddata.aws_caller_identity.currenthere) appear instate listoutput alongside resources; they don’t need importing. - Optional sub-resources you don’t have: if the plan wants to CREATE a sub-resource the real infrastructure genuinely lacks (rather than one you forgot to import), letting the apply create it is usually fine — review the diff to be sure.
Next steps
Import into a Terraform Stack
Importing into your own Terraform code? Import blocks make it much simpler.
Project config file
Manage your project’s environments and modules declaratively from source control.