When to Build a Custom Provider

6 minute read

Decision Framework

Building a custom Terraform provider is a significant investment. Before starting, carefully evaluate whether a provider is the right solution for your needs.

This chapter provides a structured decision framework, explores alternatives, and identifies when a custom provider truly makes sense.

Quick Decision Tree

Do you need to manage infrastructure?
├─ NO → Use Terraform for orchestration only
│        Consider: shell scripts, Ansible, etc.
└─ YES
   └─ Is there an existing provider?
      ├─ YES → Use or contribute to existing provider
      └─ NO
         └─ Can you use alternatives?
            ├─ YES → Explore: modules, null_resource, external
            └─ NO
               └─ Is this for production use?
                  ├─ YES → Build custom provider
                  │        (follows this tutorial)
                  └─ NO → Prototype with alternatives first

When to Build a Provider: Strong Cases

✅ Case 1: Managing a New Platform

Scenario: You have infrastructure or services with an API that needs Terraform management.

Example:

  • Internal cloud platform
  • Custom Kubernetes operators
  • Proprietary hardware management
  • SaaS platform without Terraform support

Why a provider?

  • Proper state management
  • Native Terraform workflows
  • Type safety and validation
  • Dependency management

Example:

# Your internal platform
resource "myplatform_environment" "prod" {
  name     = "production"
  region   = "us-east"
  replicas = 3
}

✅ Case 2: Heavy API Integration

Scenario: Complex API with many resources and relationships.

Characteristics:

  • 10+ resource types
  • Complex dependencies
  • State must be tracked
  • Frequent operations

Why a provider?

  • Proper CRUD lifecycle
  • State drift detection
  • Import existing resources
  • Plan/apply workflow

Anti-pattern (don’t do this):

# ❌ Bad: Using null_resource for complex API calls
resource "null_resource" "create_environment" {
  provisioner "local-exec" {
    command = "curl -X POST https://api.example.com/environments -d '{...}'"
  }
}
# No state tracking, no updates, no deletes!

✅ Case 3: Team Standardization

Scenario: Multiple teams need consistent interface to platform.

Benefits:

  • ✅ Enforces best practices
  • ✅ Centralized logic
  • ✅ Version-controlled configurations
  • ✅ Self-service infrastructure

Example:

# Standard interface across teams
module "application" {
  source = "./modules/standard-app"
  
  name = "my-service"
  # Provider handles complexity internally
}

✅ Case 4: Enterprise Requirements

Scenario: Enterprise needs governance, compliance, and auditability.

Requirements:

  • Audit trails
  • Role-based access control
  • Compliance validation
  • Approval workflows

Why a provider?

  • Integrates with Terraform Cloud/Enterprise
  • Policy-as-code support
  • Sentinel integration
  • Audit logging built-in

✅ Case 5: Existing Terraform Workflows

Scenario: Organization already uses Terraform extensively.

Benefits:

  • Consistent tooling
  • Existing CI/CD pipelines
  • Team familiarity
  • Unified state management

When NOT to Build a Provider

❌ Anti-Case 1: Simple Scripts

Scenario: One-time setup or simple configuration.

Use instead:

  • Shell scripts
  • Ansible playbooks
  • Cloud-init
  • Configuration management tools

Example:

# ❌ Don't need a provider for this
./setup-server.sh --config prod.yaml

❌ Anti-Case 2: No State Required

Scenario: Operations don’t need state tracking.

Use instead:

  • null_resource with local-exec
  • External provisioners
  • Separate automation

Example:

# ✅ Good use of null_resource (stateless operation)
resource "null_resource" "notify_slack" {
  provisioner "local-exec" {
    command = "curl -X POST ${var.slack_webhook} -d 'Deployment complete'"
  }
  
  triggers = {
    deployment_id = aws_instance.web.id
  }
}

❌ Anti-Case 3: Existing Provider Available

Scenario: Provider already exists for your platform.

Action:

  1. Check Terraform Registry
  2. Evaluate existing provider
  3. Contribute improvements instead

Search:

# Check registry
terraform init
terraform providers

❌ Anti-Case 4: Rapid Prototyping

Scenario: Exploring concepts, not building production.

Use instead:

  • Modules
  • External data sources
  • terraform_data resource

Example:

# ✅ Good for prototyping
data "external" "api_call" {
  program = ["python", "${path.module}/call_api.py"]
  
  query = {
    endpoint = var.endpoint
  }
}

❌ Anti-Case 5: Non-Infrastructure Resources

Scenario: Managing things Terraform wasn’t designed for.

Examples:

  • Application configuration (use config management)
  • User onboarding (use identity management)
  • Data processing (use workflow engines)

Alternatives to Custom Providers

Alternative 1: Modules

Best for: Reusable infrastructure patterns

module "vpc" {
  source = "terraform-aws-modules/vpc/aws"
  
  name = "my-vpc"
  cidr = "10.0.0.0/16"
}

Pros:

  • No coding required
  • Easy to share
  • Version controlled

Cons:

  • Limited to existing providers
  • No custom state logic

Alternative 2: null_resource

Best for: One-off operations, triggers

resource "null_resource" "example" {
  triggers = {
    cluster_id = aws_eks_cluster.main.id
  }
  
  provisioner "local-exec" {
    command = "kubectl apply -f manifests/"
  }
}

Pros:

  • Quick to implement
  • No provider needed

Cons:

  • Poor state management
  • Limited functionality
  • Hard to test

Alternative 3: External Data Source

Best for: Reading external data

data "external" "vault_secret" {
  program = ["./scripts/get-secret.sh"]
  
  query = {
    secret_path = "secret/data/db"
  }
}

output "password" {
  value     = data.external.vault_secret.result["password"]
  sensitive = true
}

Pros:

  • Flexible
  • Any language

Cons:

  • Read-only
  • No state management
  • No plan output

Alternative 4: External Provider

Best for: Generic command execution

resource "external" "api_call" {
  program = ["node", "api-client.js"]
  
  query = {
    action   = "create"
    resource = "environment"
    name     = "production"
  }
}

Pros:

  • Quick prototypes
  • Any language

Cons:

  • Fragile
  • Poor error handling
  • Hard to maintain

Alternative 5: terraform_data

Best for: Lifecycle management without external resources

resource "terraform_data" "example" {
  input = timestamp()
  
  triggers_replace = {
    version = var.app_version
  }
}

Pros:

  • Built-in
  • Simple lifecycle
  • No external dependencies

Cons:

  • Limited functionality
  • No API interaction

Cost-Benefit Analysis

Development Costs

Time investment:

  • Initial development: 2-8 weeks
  • Testing and debugging: 1-2 weeks
  • Documentation: 1 week
  • Ongoing maintenance: 2-4 hours/week

Expertise required:

  • Go programming
  • Terraform internals
  • API integration
  • Testing practices

Infrastructure:

  • CI/CD pipelines
  • Test environments
  • Documentation hosting

Maintenance Costs

Ongoing:

  • API changes
  • Terraform upgrades
  • Bug fixes
  • Feature requests
  • Security patches

Annual estimate: 20-40 hours/year minimum


Benefits

Quantifiable:

  • Reduced deployment time
  • Fewer manual errors
  • Standardized workflows
  • Self-service enablement

Qualitative:

  • Better developer experience
  • Improved reliability
  • Infrastructure as code benefits
  • Team scalability

Decision Matrix

Use this matrix to score your use case (1-5 scale):

Criteria Score Weight
Number of resources (1=few, 5=many) ___ 3x
Complexity of operations (1=simple, 5=complex) ___ 2x
Frequency of use (1=rare, 5=daily) ___ 3x
Team size (1=individual, 5=large org) ___ 2x
Existing provider available (1=yes, 5=no) ___ 4x
State management needed (1=no, 5=critical) ___ 3x
Budget available (1=limited, 5=ample) ___ 2x

Calculation:

Total = (Score × Weight) summed across all criteria
Maximum possible = 95

If Total > 60: Strong case for custom provider
If Total 40-60: Consider alternatives first
If Total < 40: Use alternatives

Common Use Cases

✅ Good Use Cases

  1. Internal cloud platforms
    • Custom Kubernetes operators
    • Private cloud APIs
    • Self-service portals
  2. SaaS management
    • Application configuration
    • User provisioning
    • Feature flags
  3. Hardware infrastructure
    • Network devices
    • Storage arrays
    • Custom hardware
  4. Legacy systems
    • Mainframe resources
    • Proprietary databases
    • Custom applications
  5. Compliance and governance
    • Policy enforcement
    • Approval workflows
    • Audit logging

❌ Poor Use Cases

  1. One-time migrations
  2. Simple API wrappers
  3. Prototype projects
  4. Personal projects (unless learning)
  5. Non-infrastructure resources

When to Contribute vs Build

Contribute to Existing Provider If:

  • ✅ Provider exists but missing features
  • ✅ Bug fixes needed
  • ✅ Community would benefit
  • ✅ Maintainer is responsive

Example:

# Found AWS provider missing a resource?
# Contribute instead of forking!
git clone https://github.com/hashicorp/terraform-provider-aws

Build New Provider If:

  • ✅ Platform has no provider
  • ✅ Existing provider abandoned
  • ✅ Fundamentally different approach needed
  • ✅ Internal/proprietary platform

Testing the Waters

Before committing to full provider development:

Phase 1: Prototype (1 week)

# Test with external data source
data "external" "test" {
  program = ["./scripts/api-call.sh"]
}

Validate:

  • API reliability
  • Error handling needs
  • State requirements

Phase 2: Minimal Provider (2 weeks)

Build provider with:

  • 1-2 resources
  • Basic CRUD operations
  • Minimal features

Validate:

  • Development complexity
  • Testing approach
  • Maintenance burden

Phase 3: Decision Point

Expand if:

  • Prototype successful
  • Team adopting it
  • Clear value demonstrated

Stop if:

  • Too complex
  • Low adoption
  • Better alternatives found

Real-World Examples

Example 1: PagerDuty Provider

Decision: Build custom provider

Reasoning:

  • ✅ Many resource types
  • ✅ Complex dependencies
  • ✅ Wide adoption
  • ✅ State management critical

Result: Successful, 50K+ downloads/month


Example 2: Simple REST API

Decision: Use null_resource

Reasoning:

  • ❌ Only 2 endpoints
  • ❌ No state needed
  • ❌ One-time setup

Result: Adequate solution


Example 3: Internal Platform

Decision: Build custom provider

Reasoning:

  • ✅ 20+ resource types
  • ✅ 100+ engineers using
  • ✅ Daily operations
  • ✅ Compliance required

Result: ROI positive within 6 months


Key Takeaways

  • ✅ Build providers for complex, stateful infrastructure
  • ✅ Consider alternatives for simple use cases
  • ✅ Contribute to existing providers when possible
  • ✅ Prototype before full commitment
  • ✅ Calculate cost-benefit for your scenario
  • ✅ Provider development is a long-term investment
  • ✅ Maintenance is ongoing—plan for it

What’s Next?

Ready to build a provider? Let’s set up your development environment!

Next: Development Setup →


Previous: ← Provider Architecture & GRPC

Last updated: June 30, 2026

Updated: