Skip to main content

Provider Development Pattern

When developing custom Terraform providers, you need to test them locally without publishing development versions to a registry. This pattern demonstrates how to use Terraform's development overrides with Atmos to streamline your provider development workflow.

You will learn

  • How to configure local provider binaries for testing with Atmos
  • The distinction between provider configuration and development overrides
  • Best practices for iterating on custom provider development
  • Managing development configurations across team members

The Problem

When developing custom Terraform providers, the standard workflow involves:

  1. Making code changes to your provider
  2. Publishing a new version to a registry (public or private)
  3. Updating version constraints in your Terraform code
  4. Testing the changes

This creates several pain points:

  • Slow iteration cycles - Each change requires publishing and version updates
  • Version pollution - Development versions clutter your registry
  • Testing friction - Hard to test breaking changes before they're published
  • Collaboration overhead - Sharing pre-release versions with team members is cumbersome

The Solution

Terraform provides development overrides that allow you to point Terraform directly to locally-built provider binaries. Combined with Atmos, this enables rapid iteration without publishing intermediate versions.

Understanding the Two Mechanisms

It's important to understand that provider development with Atmos involves two separate mechanisms:

1. Provider Configuration (Atmos Stack Manifests)

The providers section in Atmos stack manifests controls how providers behave - credentials, regions, endpoints, etc. Atmos serializes this to providers_override.tf.json.

stacks/catalog/myapp/defaults.yaml

components:
terraform:
myapp:
providers:
mycloud:
endpoint: "https://api.mycloud.com"
api_token: "secret-token"

This configures the provider's runtime behavior.

2. Development Overrides (Terraform CLI Configuration)

The .terraformrc file tells Terraform where to find the provider binary during development. This is Terraform's native feature, not an Atmos feature.

.terraformrc

provider_installation {
dev_overrides {
"registry.terraform.io/myorg/mycloud" = "/Users/developer/providers/bin"
}
direct {}
}

This controls where Terraform finds the provider executable.

Implementation

Step 1: Project Structure

Organize your provider development environment:

my-project/
├── providers/
│ └── terraform-provider-mycloud/ # Your provider source code
│ └── bin/ # Build output directory
└── infrastructure/
├── atmos.yaml
├── components/
│ └── terraform/
│ └── myapp/
│ ├── .terraformrc # CLI config (not committed)
│ ├── .gitignore # Exclude .terraformrc
│ ├── .terraformrc.example # Template for team members
│ ├── main.tf
│ ├── providers.tf
│ └── versions.tf
└── stacks/
└── catalog/
└── myapp/
└── defaults.yaml
Component-Level Configuration

The .terraformrc file must be in the component folder, not the infrastructure repository root. This is because Atmos executes Terraform from within the component directory, and Terraform loads CLI configuration relative to its working directory.

Step 2: Create Terraform CLI Configuration

Create .terraformrc in your component directory:

components/terraform/myapp/.terraformrc

provider_installation {
# Point to your local provider binary
dev_overrides {
"registry.terraform.io/myorg/mycloud" = "/absolute/path/to/providers/bin"
}

# For all other providers, use normal installation
direct {}
}
Path Requirements

The path in dev_overrides must be:

  • Absolute path to the directory containing the provider binary
  • The directory, not the binary itself
  • Terraform expects the binary to be named terraform-provider-<name>
Cannot Use providers.tf

The provider_installation block with dev_overrides must be in a Terraform CLI configuration file (.terraformrc or terraform.rc). It cannot be defined in .tf files like providers.tf because it's a CLI-level setting that applies across all Terraform operations, not infrastructure-specific configuration.

Step 3: Configure Environment in Atmos

Add the CLI configuration path to your component's environment:

stacks/catalog/myapp/defaults.yaml

components:
terraform:
myapp:
# Point Terraform to your CLI config in the component directory
# Since Atmos runs Terraform from the component folder, relative path works
env:
TF_CLI_CONFIG_FILE: ".terraformrc"

# Configure provider behavior (separate from dev overrides)
providers:
mycloud:
endpoint: "https://api.mycloud.com"
api_token: "{{ .settings.mycloud_api_token }}"

vars:
name: "my-application"
Relative Paths

Since Atmos executes Terraform from within the component directory (components/terraform/myapp), you can use a relative path like .terraformrc. This will correctly resolve to the file in the component folder.

Step 4: Provider Build Script

Create a build script for your provider:

providers/terraform-provider-mycloud/build.sh

#!/bin/bash
set -e

echo "Building provider binary..."
go build -o terraform-provider-mycloud

echo "Creating output directory..."
mkdir -p bin

echo "Installing provider to bin/..."
mv terraform-provider-mycloud bin/
chmod +x bin/terraform-provider-mycloud

echo "Build complete: bin/terraform-provider-mycloud"

Step 5: Development Workflow

Your development workflow now looks like this:

Provider Development Workflow

# Navigate to provider directory
cd providers/terraform-provider-mycloud

# Edit provider code
vim provider.go

# Build provider binary
./build.sh

# Navigate to infrastructure directory
cd ../../infrastructure

# Test with Atmos (uses local provider via dev_overrides)
atmos terraform plan myapp -s dev

# Repeat: edit code → build → test

Best Practices

Developer-Specific Configuration

The .terraformrc file contains paths specific to each developer's machine. Don't commit it to version control. Add it to your component's .gitignore:

components/terraform/myapp/.gitignore

# Don't commit developer-specific CLI config with local paths
.terraformrc

Instead, provide a template for team members:

components/terraform/myapp/.terraformrc.example

# Copy this file to .terraformrc and update paths for your environment
provider_installation {
dev_overrides {
# Update this path to match your local setup
"registry.terraform.io/myorg/mycloud" = "/UPDATE/THIS/PATH/providers/bin"
}
direct {}
}

Document in your component's README:

components/terraform/myapp/README.md

## Provider Development Setup

This component uses a custom provider for local development.

1. Copy `.terraformrc.example` to `.terraformrc` in this directory
2. Update the path in `.terraformrc` to point to your local provider binary location
3. Build the provider: `cd ../../../providers/terraform-provider-mycloud && ./build.sh`
4. Run Atmos commands as normal: `atmos terraform plan myapp -s dev`

Using settings for Shared Configuration

Use Atmos settings to manage shared development configuration:

stacks/orgs/acme/_defaults.yaml

terraform:
settings:
# Development mode flag
provider_dev_mode: true

# Provider configuration that can be referenced
mycloud_endpoint: "https://dev.api.mycloud.com"
mycloud_debug: true

stacks/catalog/myapp/defaults.yaml

components:
terraform:
myapp:
providers:
mycloud:
endpoint: "{{ .settings.mycloud_endpoint }}"
debug: "{{ .settings.mycloud_debug }}"

env:
TF_CLI_CONFIG_FILE: "${PWD}/.terraformrc"

Toggling Development Mode

Make it easy to switch between development and production modes:

stacks/orgs/acme/_defaults.yaml

terraform:
settings:
# Set to false to use published provider versions
provider_dev_mode: false

stacks/catalog/myapp/defaults.yaml

components:
terraform:
myapp:
# Only set CLI config in dev mode
env:
TF_CLI_CONFIG_FILE: >-
{{ if eq .settings.provider_dev_mode true }}${PWD}/.terraformrc{{ end }}
Template Syntax

The conditional templating above uses Atmos Go templates to only set TF_CLI_CONFIG_FILE when provider_dev_mode is true. This allows you to toggle development mode globally.

Provider Versioning in Development

During development, your provider won't have meaningful version numbers. Document expected behavior:

components/terraform/myapp/versions.tf

terraform {
required_providers {
mycloud = {
source = "registry.terraform.io/myorg/mycloud"
# Version is ignored when dev_overrides are active
version = "~> 1.0"
}
}
}

When dev_overrides are active, Terraform ignores version constraints and uses your local binary.

Common Patterns

Multi-Provider Development

Developing multiple providers simultaneously:

.terraformrc

provider_installation {
dev_overrides {
"registry.terraform.io/myorg/provider-a" = "/path/to/providers/bin"
"registry.terraform.io/myorg/provider-b" = "/path/to/providers/bin"
"registry.terraform.io/myorg/provider-c" = "/path/to/providers/bin"
}
direct {}
}

Team Collaboration

When multiple developers are working on the same provider:

stacks/catalog/myapp/defaults.yaml

components:
terraform:
myapp:
# Use settings to enable/disable dev mode per developer
env:
TF_CLI_CONFIG_FILE: >-
{{ if eq (env "MYCLOUD_PROVIDER_DEV") "true" }}${PWD}/.terraformrc{{ end }}

Each developer sets their own environment variable:

bash

# Developer working on provider
export MYCLOUD_PROVIDER_DEV=true

# Developer using published version
# (no environment variable set)

CI/CD Integration

Ensure your CI/CD pipeline doesn't use development overrides:

.github/workflows/terraform.yml

- name: Run Atmos
env:
# Explicitly unset to avoid using dev overrides in CI
TF_CLI_CONFIG_FILE: ""
run: |
atmos terraform plan myapp -s prod

Troubleshooting

Warning Message

When dev_overrides are active, Terraform shows a warning:

Warning: Provider development overrides are in effect

This is expected behavior and confirms your local provider is being used. To remove the warning, remove or comment out the dev_overrides block.

Provider Not Found

If Terraform can't find your provider:

  1. Check the path - Must be absolute path to the directory containing the binary
  2. Check the binary name - Must be terraform-provider-<name> (exact match)
  3. Check the permissions - Binary must be executable (chmod +x)
  4. Check TF_CLI_CONFIG_FILE - Verify the environment variable points to your .terraformrc

Debugging

# Verify environment variable
echo $TF_CLI_CONFIG_FILE

# Check binary exists and is executable
ls -la /path/to/providers/bin/terraform-provider-mycloud

# Test with verbose logging
TF_LOG=DEBUG atmos terraform plan myapp -s dev

Version Conflicts

If you see version-related errors, verify:

  1. The provider address in dev_overrides exactly matches the source in your required_providers block
  2. The provider binary name matches the provider name (last segment of the address)

Real-World Example

Here's a complete working example for developing a custom cloud provider:

Project Structure:

my-project/
├── providers/
│ └── terraform-provider-acme/
│ ├── main.go
│ ├── provider.go
│ └── bin/
│ └── terraform-provider-acme # Built binary
└── infrastructure/
├── components/
│ └── terraform/
│ └── web-app/
│ ├── .terraformrc
│ ├── .terraformrc.example
│ ├── .gitignore
│ ├── main.tf
│ ├── providers.tf
│ └── versions.tf
└── stacks/
└── catalog/
└── web-app/
└── defaults.yaml

components/terraform/web-app/.terraformrc

provider_installation {
dev_overrides {
"registry.terraform.io/acme/cloud" = "/Users/developer/work/providers/terraform-provider-acme/bin"
}
direct {}
}

components/terraform/web-app/.gitignore

.terraformrc

stacks/orgs/acme/_defaults.yaml

terraform:
settings:
# Toggle this to switch between dev and prod modes
provider_dev_mode: true

# Development API endpoint
acme_api_endpoint: "https://dev.api.acme.com"
acme_api_debug: true

stacks/catalog/web-app/defaults.yaml

components:
terraform:
web-app:
# Configure provider behavior
providers:
acme:
endpoint: "{{ .settings.acme_api_endpoint }}"
debug: "{{ .settings.acme_api_debug }}"
api_token: "{{ .settings.acme_api_token }}"

# Point to CLI config in the component directory
env:
TF_CLI_CONFIG_FILE: ".terraformrc"

vars:
app_name: "web-app"
region: "us-east-1"
Toggle Development Mode

You can use Atmos settings and templating to conditionally enable dev overrides:

env:
TF_CLI_CONFIG_FILE: >-
{{ if .settings.provider_dev_mode }}.terraformrc{{ end }}

Set provider_dev_mode: true in your global defaults when doing provider development, and false (or omit) when using published versions.

providers/terraform-provider-acme/Makefile

.PHONY: build install test

build:
@echo "Building provider binary..."
@go build -o terraform-provider-acme
@echo "Build complete: terraform-provider-acme"

install: build
@echo "Creating bin directory..."
@mkdir -p bin
@echo "Installing provider binary to bin/..."
@mv terraform-provider-acme bin/
@chmod +x bin/terraform-provider-acme
@echo "Installation complete: bin/terraform-provider-acme"

test: install
@echo "Running Atmos terraform plan..."
@cd ../infrastructure && \
atmos terraform plan web-app -s dev

components/terraform/web-app/.terraformrc.example

# Copy this to .terraformrc and update the path for your environment
provider_installation {
dev_overrides {
# Update this path to where you build the provider
"registry.terraform.io/acme/cloud" = "/absolute/path/to/providers/terraform-provider-acme/bin"
}
direct {}
}

Development workflow:

Development Workflow

# Navigate to provider directory
cd providers/terraform-provider-acme

# Edit provider source code
vim provider.go

# Build and install provider binary
make install

# Run integration test with Atmos
make test

# Test with different stack configurations
cd ../infrastructure
atmos terraform plan web-app -s dev
atmos terraform plan web-app -s staging

See Also