TrademarkTrademark
Features
Documentation

Building an IDP with Terraform & OpenTofu

Step-by-step guide to building a scalable Internal Developer Platform with Terraform & OpenTofu: automate infra, boost velocity and stay cloud-agnostic.
Sebastian StadilJune 5, 2025Updated March 31, 2026
Building an IDP with Terraform & OpenTofu
Key takeaways
  • An Internal Developer Platform (IDP) is a self-service layer that lets developers provision infrastructure autonomously within defined guardrails.
  • A Terraform or OpenTofu IDP centers on four components: a self-service developer portal, a component catalog, an IaC automation engine, and reusable versioned modules.
  • Terraform uses the BSL 1.1 license while OpenTofu uses MPL 2.0; both share HCL and the same provider ecosystem, so the choice hinges on licensing and governance preferences.
  • Best practices for an IDP include private versioned module registries, remote state with locking and encryption, CI/CD with GitOps, and Policy as Code guardrails via tools like OPA.
  • Embed security throughout by injecting secrets at runtime, enforcing RBAC and least privilege, and scanning IaC with tools like tfsec, checkov, or Trivy.

An Internal Developer Platform (IDP) is a self-service layer that lets developers provision their own resources within guardrails the platform team sets. Build it on a mature Infrastructure as Code (IaC) tool like Terraform or its open-source fork OpenTofu, and developers stop filing tickets for infrastructure. They get standard, repeatable setups instead, and ship faster because of it.

This guide assumes you have already decided to build. For the upstream decision of whether you need a custom IDP or a Terraform management platform, and how a platform team scales self-service across many developers, see our platform-engineering guide.

The IaC Foundation: Terraform vs. OpenTofu

Terraform and OpenTofu let you define infrastructure in human-readable configuration files. Both follow the same core workflow: Write (define infrastructure in HCL), Plan (preview changes), and Apply (provision or modify infrastructure).

A basic HCL example looks similar for both:

# main.tf - Example for AWS EC2 instance
provider "aws" {
  region = "us-west-2"
}
 
resource "aws_instance" "example" {
  ami           = "ami-0c55b31ad29f52381" # Example AMI
  instance_type = "t2.micro"
 
  tags = {
    Name = "ExampleInstance"
  }
}

The key difference lies in licensing and governance:

Aspect Terraform OpenTofu
Licensing Model Business Source License (BSL 1.1) Mozilla Public License 2.0 (MPL 2.0)
Core IaC Functionality Mature, extensive provider ecosystem, HCL Compatible with HCL, uses the same provider ecosystem
Community & Governance HashiCorp-led Linux Foundation, community-governed
Enterprise Offerings HCP Terraform, Terraform Enterprise Relies on 3rd-party platforms (Scalr, Spacelift, etc.)
Key Feature Divergences Ephemeral resources (example) Client-side state encryption (example)

OpenTofu started as a fork of Terraform, meant to keep the tool open source for the long haul. Its MPL 2.0 license gives teams more flexibility down the road, which matters most if you plan to build services on top of the IaC tooling.

Core IDP Components

A Terraform/OpenTofu IDP rests on four parts: a self-service interface or developer portal (Backstage, Port, or custom-built), a component catalog of modules and their metadata, an IaC automation engine that runs init/plan/apply, and a set of reusable versioned modules. The platform-engineering guide covers how those layers divide up and where a Terraform management platform fits versus a portal. The rest of this post is about building the engine and the guardrails around it.

Key Technical Considerations & Best Practices

1. Module Management

Reusable modules are the building blocks of a scalable IaC practice. They ensure consistency and embed best practices.

Referencing a module:

# Consuming a VPC module
module "vpc" {
  source  = "terraform-aws-modules/vpc/aws" # Public registry example
  version = "3.14.2"
 
  name = "my-vpc"
  cidr = "10.0.0.0/16"
 
  azs             = ["us-west-2a", "us-west-2b"]
  private_subnets = ["10.0.1.0/24", "10.0.2.0/24"]
  public_subnets  = ["10.0.101.0/24", "10.0.102.0/24"]
 
  enable_nat_gateway = true
  enable_vpn_gateway = false
}
  • Best Practice: Use private registries for internal modules, version them (Semantic Versioning), and document them thoroughly.

2. State Management

The state file tracks your managed infrastructure. Protecting it is crucial.

Backend configuration (e.g., AWS S3 with DynamoDB for locking):

# backend.tf
terraform {
  backend "s3" {
    bucket         = "my-terraform-state-bucket-unique-name"
    key            = "global/s3/terraform.tfstate"
    region         = "us-west-2"
    dynamodb_table = "terraform-state-lock-table"
    encrypt        = true
  }
}
  • Best Practices: Always use remote backends. Enable state locking, encryption at rest, and versioning. Isolate state files per environment/component to minimize blast radius. OpenTofu also offers client-side state encryption.

3. CI/CD Integration

Automate your IaC workflows for consistency and speed.

Conceptual CI/CD pipeline step (e.g., GitHub Actions):

# .github/workflows/terraform.yml (snippet)
name: 'Terraform CI/CD'
 
on:
  push:
    branches:
    - main
  pull_request:
 
jobs:
  terraform:
    name: 'Terraform'
    runs-on: ubuntu-latest
    steps:
    - name: Checkout
      uses: actions/checkout@v3
 
    - name: Setup Terraform/OpenTofu
      uses: hashicorp/setup-terraform@v2 # or opentofu/setup-opentofu
      with:
        # terraform_version: 1.5.0 # Specify version
        # tofu_version: 1.6.0 # For OpenTofu
 
    - name: Terraform Init
      run: terraform init # or tofu init
 
    - name: Terraform Validate
      run: terraform validate # or tofu validate
 
    - name: Terraform Plan
      if: github.event_name == 'pull_request'
      run: terraform plan -no-color -input=false # or tofu plan
      continue-on-error: true
 
    - name: Terraform Apply
      if: github.ref == 'refs/heads/main' && github.event_name == 'push'
      run: terraform apply -auto-approve -input=false # or tofu apply
  • Best Practice: Integrate init, validate, plan, and apply stages. Use GitOps principles: store IaC in Git, use Pull/Merge Requests for changes, and automate plan reviews.

4. Policy Enforcement

Implement guardrails using Policy as Code (PaC).

Conceptual Rego policy snippet (for Open Policy Agent - OPA):

package terraform.aws.security
 
# Deny if an S3 bucket does not have versioning enabled
deny[msg] {
  input.resource_changes[_].type == "aws_s3_bucket"
  not input.resource_changes[_].change.after.versioning[_].enabled
  msg = "S3 buckets must have versioning enabled."
}
 
# Deny overly permissive security group ingress rule
deny[msg] {
  sg_rule = input.resource_changes[_]
  sg_rule.type == "aws_security_group_rule"
  sg_rule.change.after.type == "ingress"
  contains(sg_rule.change.after.cidr_blocks[_], "0.0.0.0/0")
  sg_rule.change.after.protocol == "-1" # All protocols
  msg = "Security group rule allows all traffic from any source. Please restrict."
}
  • Best Practice: Integrate PaC tools like OPA into CI/CD pipelines to validate plans against organizational policies for security, compliance, and cost.

5. Security

Embed security throughout the IDP.

  • Secrets Management: Never hardcode secrets. Integrate with tools like HashiCorp Vault, AWS Secrets Manager, etc., to inject secrets at runtime.
  • RBAC: Implement Role-Based Access Control for the IDP and ensure IaC tools operate with least privilege. Scalr, a drop-in Terraform Cloud alternative, lets you compose custom roles from individual permissions across account, environment, and workspace scopes. That granularity buys you two things. It enforces least privilege, so every role gets only the access it needs. And it lets you onboard more personas safely for self-service: a junior engineer who can plan but not apply, an app team scoped to its own environment, an auditor with read plus audit access, or a contractor limited to a single workspace.
  • Vulnerability Scanning: Use tools like tfsec, checkov, or Trivy to scan IaC code for misconfigurations.

Conclusion

An IDP built on Terraform or OpenTofu gives developers a faster path to infrastructure and gives the platform team consistent, governed setups to maintain. Most of the work is in the parts covered above: reusable modules, protected state, automated pipelines, policy checks, and security baked into each step. Get those right and you have a platform people actually use instead of working around. The choice between Terraform and OpenTofu mostly comes down to licensing, and OpenTofu is the open-source option if that is what your team needs.

About the author
Sebastian StadilCEO at Scalr
Sebastian Stadil is the CEO of Scalr with 15+ years of DevOps experience. He started with AWS in 2004 and advised early Microsoft Azure and Google Cloud.