There are two senses of "tenant" in play here, and conflating them causes real confusion — so name them up front:
Tenant entities you place on clusters. This is what the rest of Best Practices is about.The second half is self-hosting: running the whole cattle wedge on your own infrastructure with no SaaS dependency — the hard constraint the OSS-first persona (Sam) lives by.
Terrantula organizes work as projects inside organizations, with role-based access control over each project's catalog and data.
Do put each independent fleet — each blast-radius boundary — in its own project. A project is the unit that holds one catalog (entity types, cells, relationships, Actions, secrets) and its entities.
Don't cram unrelated fleets into one project to save setup.
Because a project is the access and isolation boundary. RBAC, API tokens, and secrets are scoped to it. Two fleets that shouldn't share credentials, placement pools, or audit trails should not share a project. The right grain is usually one project per team-owned fleet, or per strongly-isolated environment.
Do keep prod and non-prod fleets isolated, and use envOverrides on Actions to point one definition at different repos, workspaces, or runner endpoints per environment.
Don't mix dev and prod entities in one cell, and don't reuse a prod secret in a dev Action.
Because environment isolation is the same discipline you already apply to Terraform state. A cell is a placement candidate set — mixing environments invites placing a prod tenant on a dev cluster. envOverrides lets you keep one Action definition (so the placement and lifecycle logic doesn't fork) while the trigger targets the right environment at dispatch. See the Environments how-to for the end-to-end shape.
Do grant the narrowest role that lets someone do their job, and split read from write:
Don't hand everyone the highest role because it's less friction.
Because Terrantula's permission model splits along resource verbs (catalog:read, data:write, secrets:set-value, apply:write, …) precisely so you can grant visibility without granting the ability to fire Actions. The full role-and-permission vocabulary is in the RBAC reference. Visibility-only access is a legitimate, common posture — many teams start there.
Do mint a dedicated, narrowly-scoped project API token for each CI pipeline or script (terr_…), and rotate them.
Don't reuse a human's session token in automation, or share one token across many pipelines.
Because terr_-prefixed tokens are hashed before storage and scoped to a project's RBAC — a token is the audit identity for everything it does. A per-pipeline token means a leaked or rotated credential has a contained blast radius and a clear owner. The CLI reference covers token use in CI.
Do declare secrets by name in the catalog and set their values out-of-band with terrantula secrets set-value.
Don't commit a credential into catalog YAML, even in a private repo.
Because secret values are encrypted at rest with the server's ENCRYPTION_KEY and never appear in your catalog, version control, or logs. A per-environment, per-project secret of the same name (github-token, tfc-api-token) keeps prod and dev credentials genuinely separate while your Action YAML stays identical.
The Terrantula backend is Apache 2.0 and self-hostable end-to-end. The entire cattle wedge — multi-tenancy, governance, Actions, the cascade — runs with zero SaaS dependencies. This is a binding product commitment, not a tier: no cattle-wedge feature is gated behind a paywall.
The OSS-first test is simple: could you run the whole wedge with no paid subscription?The answer must always be yes. Terrantula OSS + your Terraform substrate + your own CI = full functionality at $0 SaaS spend. If a workflow seems to require subscribing to something, you've taken a wrong turn — re-check it against this constraint.
Do run the API and worker against your own Postgres (14+), set DATABASE_URL and ENCRYPTION_KEY, and deploy with the Kubernetes/Helm guide.
Don't expect Terrantula to host your state, your runners, or your Postgres.
Because Terrantula owns exactly one layer — the fleet model above your Terraform — and nothing below it. You keep your state backend, your runner, your CI. The queue is Postgres-native (pg-boss), so a working deployment is just the API, the worker, and a database — no extra infrastructure to operate. Full configuration is in the install guide.
Do point Actions at the substrate you already run: the pull-request trigger for bare Terraform + GitHub Actions, terraform-cloud for a TFC/HCP workspace, atlantis for your Atlantis, atmos-workflow for Atmos.
Don't look for a hosted Terrantula runner pool — there isn't one, by design.
Because Terrantula sits on top of runners and never replaces them. For substrates without a hosted API — Atmos and Atlantis — Terrantula ships reference runner implementations you deploy yourself (a customer-deployed wrapper, not a Terrantula-hosted service). The Atmos workflow trigger calls your reference runner endpoint; nothing routes through a hosted service unless you deliberately choose the hosted option. Substrate priority is Terraform first, Atmos as a peer, OpenTofu second.
Do use the same catalog YAML, the same Actions, and the same first-five-minutes onboarding (terrantula import-terraform → terrantula dashboard) self-hosted as you would hosted.
Because self-hosting is a deployment choice, not a different product. The local-first path (an embedded SQLite DB, no signup) is for one person trying it out; a self-hosted server with Postgres is for a team. The catalog, the personas it serves, and every commitment on these pages are identical across all three. When you outgrow local mode, terrantula login points the same commands at your server.
Do reach for the hosted option if you'd rather not operate Postgres yourself, or want managed multi-tenancy, SSO/OIDC, and audit out of the box.
Don't assume the hosted option unlocks fleet features the OSS path lacks — it doesn't.
Because the hosted offering adds operations, support, and the polished UI on top of the same Apache 2.0 backend; it doesn't fork the API or gate the wedge. The self-hosted path is never second-class.
Back to: Best Practices overview · Reference: RBAC roles & permissions · Triggers · Deploy on Kubernetes