
This Guide is just as valid for OpenTofu as it is for Terraform! Backstage is an open-source framework for building internal developer portals. When you wire Terraform into it, developers can provision their own infrastructure from a portal they already use, and the platform team gets to standardize how that infrastructure gets built. This guide walks through the ways to connect the two and what each approach costs you in effort and flexibility.
The point is developer self-service. With Terraform behind Backstage, a developer who needs a database or a cloud storage bucket can request it from the portal instead of filing a ticket and waiting. They don't have to know HCL or the cloud provider's API. And because every request runs through templates the platform team has already vetted, the infrastructure that comes out the other end follows the same security and governance rules every time. That combination addresses the usual complaints: provisioning is slow, tooling drifts from team to team, and nobody can find what already exists.
Several paths exist for this integration:
The Open-Source Route: Using Backstage's Scaffolder with custom CI/CD pipelines to run Terraform CLI commands offers maximum flexibility. This approach provides complete control but demands significant engineering effort for setup, maintenance, and security.
Terraform Cloud: HashiCorp's managed service can offload Terraform execution and state management. Backstage plugins can provide visibility and sometimes control over Terraform Cloud workspaces. This simplifies operations but introduces costs and reliance on the HashiCorp ecosystem, which is exclusively Terraform-centric.
Terraform Cloud Alternatives like Scalr: Platforms such as Scalr offer a compelling alternative for managing Terraform (and other tools like OpenTofu and Terragrunt) at scale, presenting distinct advantages in a Backstage integration.
See the officially supported Scalr plugin with Backstage here:
// packages/app/src/components/catalog/EntityPage.ts
+ import { EntityScalrEnvironmentContent } from '@scalr-io/backstage-plugin-scalr';
const domainPage = (
<EntityLayout>
<EntityLayout.Route path="/" title="Overview">
<Grid container spacing={3} alignItems="stretch">
{entityWarningContent}
<Grid item md={6}>
<EntityAboutCard variant="gridItem" />
</Grid>
<Grid item md={6} xs={12}>
<EntityCatalogGraphCard variant="gridItem" height={400} />
</Grid>
<Grid item md={6}>
<EntityHasSystemsCard variant="gridItem" />
</Grid>
</Grid>
</EntityLayout.Route>
+ <EntityLayout.Route path="/scalr" title="Scalr">
+ <EntityScalrEnvironmentContent />
+ </EntityLayout.Route>
</EntityLayout>
);// backstage-tfc-plugin-component.ts (Simplified)
import { tfeApiRef } from '@backstage/plugin-terraform-common';
// ...
async function provisionWithTFC(workspaceId: string, variables: Record<string, any>) {
const tfeApi = useApi(tfeApiRef);
try {
// 1. Create a new run
const run = await tfeApi.createRun({
workspaceId,
message: 'Run triggered from Backstage',
// Potentially set autoApply: true if desired
});
// 2. (Optional) Upload configuration versions if not VCS-driven
// 3. (Optional) Set variables for the run
await tfeApi.setVariables({
workspaceId,
runId: run.id, // Or apply to workspace directly
variables: Object.entries(variables).map(([key, value]) => ({
key,
value: String(value),
category: 'terraform',
sensitive: false,
})),
});
// 4. (If not auto-apply) Apply the run after plan review
// This might involve manual steps in TFC UI or further API calls
console.log(`Terraform Cloud run ${run.id} created. Review and apply in TFC.`);
} catch (error) {
console.error('Error provisioning with Terraform Cloud:', error);
}
}# scaffolder-template.yaml
# ...
steps:
- id: trigger-terraform-pipeline
name: Trigger Terraform CI/CD Pipeline
action: 'http:request' # Or a custom action for your CI/CD system
input:
url: 'https://my-ci-cd.example.com/api/v1/trigger'
method: 'POST'
headers:
Content-Type: 'application/json'
Authorization: 'Bearer ${{ parameters.ciCdToken }}'
body:
repository: '${{ parameters.gitRepoUrl }}'
branch: 'main'
terraformAction: 'apply'
variables:
instance_type: '${{ parameters.instanceType }}'
region: '${{ parameters.region }}'
# ...In the CI/CD pipeline (e.g., Jenkins, GitLab CI, GitHub Actions):
# ci-cd-pipeline-script.sh
terraform init
terraform validate
terraform plan -out=tfplan -var="instance_type=$INSTANCE_TYPE" -var="region=$REGION"
# Potentially add an approval gate here
terraform apply -auto-approve tfplanEach approach works, but the more complex your environment, the more the third option tends to pay off. Scalr lets teams keep their existing state storage instead of moving everything to a proprietary backend, which matters if you already have established practices you don't want to unwind.
Scalr also organizes accounts, environments, and workspaces in a hierarchy, with credentials and policies shared down that tree. For a large organization, that structure usually maps onto how teams are actually arranged, so governance can be set once and inherited rather than configured workspace by workspace. The more Backstage becomes the single front door to your infrastructure, the more the platform behind it has to handle that breadth without getting in the way.
Scalr also has an officially supported plugin for Backstage.
Self-service through Backstage multiplies the number of workspaces a platform team has to oversee, so that team needs a way to watch the whole fleet. Scalr gives account-wide reports across every workspace: resources, modules, providers, versions, drift, and stale workspaces. It also adds queued-run and pending-approval metrics and event streaming to Datadog, so a platform engineer can spot a stuck team and step in to raise a quota, fix a policy, or unblock a run.
| Feature | Open-Source (Custom CI/CD) | Terraform Cloud | Scalr (and similar alternatives) |
|---|---|---|---|
| Control & Flexibility | Very High | Medium | High |
| Engineering Effort | High | Low | Medium |
| Cost | Potentially Low (infra) | Per-resource (RUM) | Usage-based (per run) |
| State Management | Self-managed | Managed by TFC | Flexible (self or managed by Scalr) |
| Governance | Custom implementation | TFC Policies (OPA) | Scalr Policies (OPA), RBAC, Hierarchy |
| Multi-IaC Tooling | Yes (with custom work) | Terraform only | Yes (Terraform, OpenTofu, etc.) |
| Vendor Lock-in | Low | Medium (HashiCorp ecosystem) | Low |
| Setup Complexity | High | Medium | Medium |
| Scalability | Depends on CI/CD setup | High | High |
| Community Support | Large (Backstage, TF) | Large (Terraform) | Growing |
Which path you pick comes down to your requirements, what you already run, how much engineering time you can spend, and your budget. The open-source route gives you the most customization but asks the most of your team. Terraform Cloud is managed and easy to start with, though it stays Terraform-specific. If you care most about flexible governance in a complicated environment and want support for more than just Terraform, Scalr is worth a look. Whatever you choose, the aim is the same: developers get infrastructure quickly, and the platform team keeps it governed.
