Core Concepts

The Cattle Mindset gave you the lens. This page gives you the vocabulary. Everything in Terrantula is built from a small set of primitives, and once you know all of them, the YAML you write later is just these concepts spelled out.

We'll teach them in dependency order — each concept builds on the one before it. There are no commands here. Read it once start to finish; you'll recognize every term when it shows up in real config.

A useful frame before we start: Terrantula separates types from instances, the same way a class differs from an object. You define the shape of a tenant once (the type), and then you have many actual tenants (the instances). Most pages of this section are about getting the types right, because the instances mostly come from your imported Terraform or get created by Actions.

Entity Types

An Entity Type is the shape of a thing in your fleet. It's a definition, not a thing itself.

A Tenant entity type might say: a tenant has a customer_id, a plan_tier (one of basic or premium), a contact email, and a lifecycle that moves through pending → provisioning → active → suspended → deprovisioning. A TenantCluster entity type might say: a cluster has a region, a tier, a Kubernetes version, and an ARN.

An entity type carries a few kinds of information:

  • Properties — the typed fields every instance has (region, plan_tier, contact_email). Properties can be required, can have enums, and have types like string or number.
  • States — the lifecycle states an instance can occupy, and which one it starts in. This is what lets Terrantula answer "how many tenants are active right now?"
  • Metrics — measured values attached to instances, often derived automatically. A cluster's tenant-count metric, for example, is computed from how many active relationships point at it — Terrantula recomputes it as the fleet changes, so capacity decisions reflect real allocation rather than a stale number.
  • Constraints — hard limits expressed against a metric. "No cluster may host more than fifty tenants" is a constraint on the tenant-count metric.

The entity type is where you formalize a concept that's currently implicit in your infrastructure. "All our prod clusters are this shape" becomes a written definition instead of a pattern in your head.

Entities

An Entity is an instance of an entity type — one actual tenant, one actual cluster, one actual database. It's a typed, stateful record of something that exists.

Each entity has the properties its type declares, sits in one of its type's states, and carries any metrics its type defines. When you import Terraform state, every matching resource becomes an entity. When an Action onboards a new tenant, it creates an entity.

The key thing to internalize: an entity is cattle, not a pet. You rarely care about one entity in isolation. You care about the population — how many, in what states, with what capacity used. Terrantula's whole job is to make the population legible and governable.

Entities are a projection of your real infrastructure

An entity reflects state derived from Terraform; it isn't the source of truth. You don't change what a tenant is by editing its entity directly. Real changes flow through Actions → pull requests → your CI's terraform apply, and the entity updates to match. The graph stays honest because Terraform stays in charge.

Cells

A Cell is a named group of entities with a placement policy and aggregate constraints. If entity types answer "what is a thing," cells answer "where should the next thing go, and what limits apply across the group."

Say you run a fleet of production clusters. You'd put them in a prod-clusters cell. The cell carries:

  • A placement policy — how Terrantula ranks members when something needs a home. least-loaded (the default) puts the next tenant on the cluster with the lowest load; round-robin distributes evenly; random picks at random.
  • Aggregate constraints — limits across the whole cell, like "no more than five hundred tenants summed across every cluster in this cell." This is distinct from a per-cluster constraint on the entity type; the cell limit governs the fleet, the entity-type limit governs the individual.

Cells are the home of the fleet decision — the placement call that Terraform was never going to make for you. When you ask Terrantula to onboard a tenant, the cell's policy is what chooses the target cluster, and the cell's constraints are what stop the request if the fleet is full.

Membership is usually explicit (you add clusters to the cell) but can also be derived from a property value, so members are computed automatically. Most people start explicit.

Relationship Types

A Relationship Type is the shape of a connection between two entity types. It's directional and typed, just like an entity type — a template for the actual links.

A runs_on relationship type might say: a Tenant runs on a TenantCluster, the cardinality is many-to-one (many tenants per cluster, one cluster per tenant), the relationship moves through states like assigning → active → migrating → removing, and it carries properties of its own such as the per-tenant namespace.

Cardinality is load-bearing. many-to-one, one-to-one, one-to-many, and many-to-many aren't just documentation — Terrantula enforces them. You can't accidentally assign one tenant to two clusters if the type says one cluster per tenant. The graph stays consistent because the rules are part of the type.

Relationships

A Relationship is an instance of a relationship type — one actual link between two specific entities. "Tenant acme runs on cluster eks-us-east-1, in the active state, in namespace tenant-acme."

Relationships are how the fleet hangs together. They're also how Terrantula derives metrics (a cluster's tenant-count is just a count of active runs_on relationships pointing at it) and how lifecycle hygiene works (the cascade, below, walks relationships to figure out what to tear down and in what order).

Actions

An Action is a declarative workflow you fire against entities. Actions are where Terrantula stops being a read-only catalog and starts doing things — but always within the rules.

A typical Action — OnboardTenant — bundles a lot of decisions into one declaration:

  • Parameters — the inputs the operator provides (customer_id, plan_tier, region_preference).
  • Recommendations — placement queries against a cell. "Pick the least-loaded cluster in prod-clusters." This is where cell placement gets invoked.
  • Conditions — guards that must pass for the Action to be available (e.g. only allow SuspendTenant on tenants that are currently active).
  • An operation — what changes in the graph: create an entity, transition its state, create a relationship. The operation declares the entity's state onTrigger, onSuccess, and onFailure, so the lifecycle is explicit.
  • A trigger — how the change reaches your real infrastructure.

That last part is the heart of it:

Actions open pull requests — they never run

terraform apply When an Action needs to change infrastructure, its trigger opens a PR against your repository with the change (or, for hosted runners with an API, fires a run via that API). Your CI runs the apply on merge. Terrantula validates parameters, evaluates conditions, enforces constraints, and runs placement — all beforethe PR opens — then tracks the outcome and transitions the entity when the change lands. Terrantula never executes Terraform itself.

Triggers are pluggable, which is how Terrantula sits on top of whatever you already run:

  • pull-request — commits files to a branch and opens a GitHub PR. The default for bare Terraform plus GitHub Actions.
  • terraform-cloud — fires a run against an existing Terraform Cloud / HCP Terraform workspace via its API and tracks the outcome.
  • atmos-workflow — invokes a named Atmos workflow on a customer-deployed runner.
  • atlantis — opens a PR that your Atlantis instance picks up, or calls its API directly.
  • webhook — a generic HTTP call for anything else.

Whichever trigger you choose, Terrantula tracks every firing as an ActionRun — a record of who fired what, with what parameters, and how it ended. That's your audit trail.

Secrets

A Secret is a named, encrypted credential that Actions reference without ever embedding the value in your catalog. A tfc-api-token secret, a github-token secret, a webhook signing secret.

In your YAML you reference a secret by name — {{ secrets.tfc-api-token }} — and set its actual value out-of-band. The value is encrypted at rest and never appears in your catalog config, your version control, or your logs. This is how an Action can authenticate to Terraform Cloud or open a PR without leaking the credential into the declarative model.

The cascade

The cascade is what makes lifecycle hygiene automatic — and it's the concept that most directly solves the "deprovisioning rot" pain from the mindset page.

When an entity changes state, the cascade walks its relationships and propagates the change to connected entities and relationships, in order. The classic example is deprovisioning: when a tenant enters deprovisioning, a cascade rule transitions its runs_on relationships to removing, which can in turn drive the teardown of dependent infrastructure — in dependency order, so nothing is removed before the things that depend on it.

Cascade rules fire at well-defined phases of an ActionRun:

  • on-trigger — when the run is created, before the external trigger fires.
  • on-success — when the run reports success.
  • on-failure — when the run reports failure.

The cascade is why the graph is more than a pretty picture. Because relationships carry cardinality and direction, Terrantula knows what depends on what, and can answer "tear this down safely" — the question that used to live in a runbook nobody read.

How it all fits together

Read top to bottom, the model is a stack:

Entity types define shapes → entities are the instances → cells group entities and decide placement → relationship types define connections → relationships are the actual links → Actions change the graph and open PRs → secrets authenticate those Actions → the cascade keeps lifecycle changes ordered and clean.

Terraform still provisions. Terrantula models the fleet, decides placement, enforces capacity, and drives ordered lifecycle changes through PRs your CI applies.

You now have the whole vocabulary. The next page shows you how it's spelled out in the declarative catalog YAML — the shape you'll actually write.


Next: Catalog YAML Guide → — these concepts, mapped onto real YAML.