Every fleet starts from infrastructure that already exists. Before you write a single entity type by hand, you bring your real Terraform state in as entities — so you're modeling reality, not a blank page. This is the cattle on-ramp: a graph view of what you actually run, in one command.
By the end of this step you'll have your existing clusters (and whatever else is in your state) showing up as entities in Terrantula's local database, ready to shape in Step 2.
terrantula import terraform reads a Terraform state file, maps each resource to a Terrantula entity using a small mapping config you provide, and applies the result. It's the same apply pipeline you'd use for hand-written catalog YAML — import just generates the entity documents for you from your state.
Two things make it safe to run against real infrastructure:
--replace.Import needs to know which Terraform resource types become which entity types, and how to fill in their properties. You give it that in a YAML mapping config. Here's a minimal one — save it as tf-mapping.yaml:
Read it top to bottom:
sources — the state file(s) to read. labels here tag every entity from this source (handy for filtering later).mappings — one entry per Terraform resource type. The key (aws_eks_cluster) is the TF type; entity_type is the Terrantula entity type each matching resource becomes.name and properties use Go-template interpolation against each resource's attributes. {{ .name }} is the resource's name; {{ .arn | split ":" | index 3 }} pulls the region out of an EKS ARN; | default "..." supplies a fallback when an attribute is absent.You don't have to write this from scratch. Terrantula ships starter configs you can compose with extends: — aws-multi-account, kubernetes-inventory, and multi-tenant-cells cover the common shapes. To build on one, reference it and override just what you need:
For your first run, point extends: at the starter closest to your stack and let it map the obvious resource types. Once you see what landed in the graph, add explicit mappings: overrides to rename entity types or compute properties the way your fleet thinks about them. The starter files under packages/cli/src/starters/are the format reference.
Point the command at your state file and your mapping config:
--state is a URI: a local path, s3://bucket/key, or tfc://org/workspace. Repeat the flag to merge several state files into one fleet. --config is the path to the mapping config above.
You'll see a summary like:
Notice import auto-creates a stub TenantCluster entity type if one doesn't exist yet — enough for the entities to land. In Step 2 you'll replace that stub with a proper, fully-typed definition.
Add --dry-run (or its alias --plan, for terraform planmuscle memory) to see exactly what would be created or changed without writing anything. Run it once to sanity-check your mapping, then drop the flag to apply for real.
Open the local fleet view:
Your browser opens to the entity browser. You'll see your clusters as TenantCluster entities — your real infrastructure, on one screen, derived straight from state. There are no tenants and no relationships yet; that's exactly what the next steps add.
The entities you just imported are a projection. You don't change what a cluster isby editing it here. Real changes still flow through Terraform; re-running the import refreshes the projection. The dashboard stays honest because Terraform stays in charge.
After applying entities, the importer scans for property values that match
another entity's name across types. When it finds a match — say, a tenant whose
cluster property equals the name of a Cluster entity — it suggests a
derivedFrom relationship type as commented YAML:
The importer never auto-creates domain relationships. It suggests structural
facts — cardinality, the join property — and leaves naming and containment to you.
Promote the suggestion by adding a suggest_relationships.overrides entry to your
mapping config. For example, to rename tenant_cluster to runs_on with explicit
containment:
The next run emits name: runs_on and containment: true (committed, not hinted).
When you're satisfied, paste the uncommented block into your blueprint and apply it.
The suggestion engine works across statefiles. If your tenant stack references your
cluster stack via terraform_remote_state, the importer detects that the tenant's
cluster property value came from a remote-state output and annotates the suggestion:
The (cross-stack via clusters) note names the terraform_remote_state data source
that exposed the value. The entity also gains a tf_source_stack: clusters label
for filtering and provenance tracking.
At query time the derived edge resolves by joining Tenant.cluster == Cluster.name
across all entities in the environment — regardless of which statefile each entity
came from. There is no Terraform reference required at resolve time; the join is on
entity properties.
A complete worked example — two statefiles, a mapping config with overrides, and the
full importer output — lives in
examples/cattle-multi-stack-greenfield/import-fixtures/.
terraform apply in sight.terrantula dashboard.You now have entities, but they're shaped by a thin auto-generated stub. Next you'll give them a proper definition — lifecycle states, typed properties, derived metrics — and group your clusters into a cell that can place tenants.
Prev: ← Your First Fleet (overview) · Next: Step 2 — Define entity types & cells →