Skip to content

How to Build and Publish Your Own Terraform Provider

Creating your own Terraform provider is like opening a gateway into the Terraform ecosystem — you’re not just using infrastructure as code, you’re extending it. With a provider, you can define custom resources and data sources that interact with APIs, platforms, or even your own tools. Here’s a complete guide on how to build one from scratch and publish it to the Terraform Registry.


Understanding What a Provider Is

A Terraform provider is a Go-based plugin that tells Terraform how to manage external systems. Think of it as a translator — it takes your Terraform configuration and converts it into API calls to create, read, update, and delete resources.

For example:

hcl
resource "mycloud_instance" {
  name = "server-1"
}

Behind the scenes, that simple block might call your cloud provider’s API to spin up a real virtual machine.


Step 1: Setting Up Your Environment

You’ll need the following tools:

  • Go 1.22+
  • Terraform CLI
  • GitHub account (for publishing)
  • Go modules enabled

On Linux or macOS:

bash
sudo apt install golang terraform git

Then initialize your Go module:

bash
mkdir terraform-provider-myprovider
cd terraform-provider-myprovider
go mod init github.com/myorg/terraform-provider-myprovider

Your project structure will look like this:

.
├── main.go
├── provider.go
├── resource_example.go
├── go.mod
└── go.sum

Step 2: Writing the Provider Code

Here’s the heart of your provider: the code that defines what Terraform can do with it.

main.go

go
package main

import (
	"context"
	"flag"
	"log"

	"github.com/hashicorp/terraform-plugin-framework/providerserver"
	"github.com/myorg/terraform-provider-myprovider/myprovider"
)

var version = "0.1.0"

func main() {
	flag.Parse()
	err := providerserver.Serve(context.Background(), myprovider.New, providerserver.ServeOpts{
		Address: "registry.terraform.io/myorg/myprovider",
	})
	if err != nil {
		log.Fatal(err.Error())
	}
}

provider.go

go
package myprovider

import (
	"context"

	"github.com/hashicorp/terraform-plugin-framework/provider"
	"github.com/hashicorp/terraform-plugin-framework/types"
)

type MyProvider struct{}

func (p *MyProvider) Metadata(_ context.Context, _ provider.MetadataRequest, resp *provider.MetadataResponse) {
	resp.TypeName = "myprovider"
}

func (p *MyProvider) Schema(_ context.Context, _ provider.SchemaRequest, resp *provider.SchemaResponse) {
	resp.Schema = map[string]provider.Schema{
		"api_key": {Type: types.StringType, Optional: true},
	}
}

func (p *MyProvider) Resources(_ context.Context) []func() provider.Resource {
	return []func() provider.Resource{
		NewExampleResource,
	}
}

func (p *MyProvider) DataSources(_ context.Context) []func() provider.DataSource {
	return nil
}

func New() provider.Provider {
	return &MyProvider{}
}

Step 3: Adding a Custom Resource

Let’s add a simple resource to demonstrate functionality.

resource_example.go

go
package myprovider

import (
	"context"

	"github.com/hashicorp/terraform-plugin-framework/resource"
	"github.com/hashicorp/terraform-plugin-framework/types"
)

type ExampleResource struct{}

func NewExampleResource() resource.Resource {
	return &ExampleResource{}
}

type ExampleResourceModel struct {
	ID   types.String `tfsdk:"id"`
	Name types.String `tfsdk:"name"`
}

func (r *ExampleResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
	resp.TypeName = req.ProviderTypeName + "_example"
}

func (r *ExampleResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
	resp.Schema = map[string]resource.Schema{
		"name": {Type: types.StringType, Required: true},
	}
}

func (r *ExampleResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
	var data ExampleResourceModel
	req.Plan.Get(ctx, &data)
	data.ID = types.StringValue("example-id")
	resp.State.Set(ctx, &data)
}

This simple resource creates an “example” object with an ID and a name — just enough to show Terraform that it works.


Step 4: Build and Test Locally

Compile the provider:

bash
go build -o terraform-provider-myprovider

Create a local Terraform configuration:

hcl
terraform {
  required_providers {
    myprovider = {
      source = "myorg/myprovider"
      version = "0.1.0"
    }
  }
}

provider "myprovider" {
  api_key = "12345"
}

resource "myprovider_example" "test" {
  name = "hello"
}

To make Terraform recognize your local provider:

bash
mkdir -p ~/.terraform.d/plugins/registry.terraform.io/myorg/myprovider/0.1.0/linux_amd64
mv terraform-provider-myprovider ~/.terraform.d/plugins/registry.terraform.io/myorg/myprovider/0.1.0/linux_amd64/

Now run:

bash
terraform init
terraform apply

You should see Terraform successfully create your example resource.


Step 5: Publishing to the Terraform Registry

  1. Push your code to GitHub Repository name must follow:

    terraform-provider-myprovider
    
  2. Create a semantic version tag:

    bash
    git tag v0.1.0
    git push origin v0.1.0
    
  3. Go to the Terraform Registry

    • Log in with GitHub
    • Choose “Publish Provider”
    • It will automatically detect your repo and tagged release

Step 6: Automate Releases with GitHub Actions

HashiCorp expects signed binaries for multiple platforms (Linux, macOS, Windows). Use the official scaffolding framework as your base. It includes GitHub Actions that build, test, and publish your provider automatically.


Step 7: Use Your Provider in Terraform

Once published, anyone can use your provider just like official ones:

hcl
terraform {
  required_providers {
    myprovider = {
      source = "myorg/myprovider"
      version = "~> 0.1"
    }
  }
}

Run terraform init, and Terraform will download it directly from the Registry.


The Bigger Picture

Writing your own Terraform provider is not just about custom infrastructure automation — it’s about interoperability. You’re effectively extending Terraform’s language so it can talk to anything with an API. That means your internal services, your IoT devices, or your own cloud tools can now live inside Terraform’s declarative universe.


Building a provider is a beautiful way to blur the line between software and infrastructure. You’re not just deploying systems — you’re teaching Terraform to understand new worlds.