
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.
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.
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.
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
}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
}
}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 applyinit, validate, plan, and apply stages. Use GitOps principles: store IaC in Git, use Pull/Merge Requests for changes, and automate plan reviews.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."
}Embed security throughout the IDP.
tfsec, checkov, or Trivy to scan IaC code for misconfigurations.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.
