Back to Blog
Open Source3 min read

Building the Sigilhosting Terraform Provider: Lessons From 14 Months of Development

Our Terraform provider now covers 28 resource types across compute, networking, DNS, and load balancing. Here's what we learned about building a production-quality provider from scratch.

TH
Tomás Herrera · Backend Engineer

Why we built our own

When we launched the Sigilhosting API in early 2024, several community members immediately started building unofficial Terraform providers. We appreciated the effort, but the providers had coverage gaps, inconsistent error handling, and no guaranteed maintenance. Rather than coordinating across multiple community efforts, we decided to build and maintain an official provider ourselves.

The decision came down to a core belief: infrastructure-as-code is not optional for serious users. If our Terraform provider is unreliable or incomplete, customers can't treat Sigilhosting the same way they treat AWS or GCP in their configurations. We'd always be the "manual step" in otherwise automated workflows — and that eventually drives customers to providers with better IaC support.

Architecture decisions

We built the provider using HashiCorp's terraform-plugin-framework (the newer SDK, not the legacy terraform-plugin-sdk). The framework's type system catches more bugs at compile time and makes it easier to implement complex nested attributes like load balancer routing rules and firewall policies.

// Sigilhosting VPS resource schema (simplified)
func (r *VPSResource) Schema(_ context.Context, 
    _ resource.SchemaRequest, 
    resp *resource.SchemaResponse) {
    
    resp.Schema = schema.Schema{
        Attributes: map[string]schema.Attribute{
            "region": schema.StringAttribute{
                Required: true,
                PlanModifiers: []planmodifier.String{
                    stringplanmodifier.RequiresReplace(),
                },
            },
            "plan":    schema.StringAttribute{Required: true},
            "image":   schema.StringAttribute{Required: true},
            "ssh_keys": schema.SetAttribute{
                ElementType: types.StringType,
                Optional: true,
            },
        },
    }
}

Resource coverage

Resource Coverage Growth · 14 Months Sep 2024 Dec 2024 Apr 2025 Aug 2025 Nov 2025 0 10 20 30 6 v0.1 Launch VPS · SSH · FW · Net · FIP · DNS 15 v0.8 Networking LB · SSL · Private Net · FW rules 24 v1.0 Kubernetes Clusters · node pools · volumes 28 v1.4 Current 342 acceptance tests · real API ~45 min per run · ~$12 compute cost Import for all 28 resources Auto-generated from OpenAPI spec
Terraform provider resource coverage — 6 initial resources expanded to 28 over 14 months

We shipped with 6 resource types (VPS, SSH key, firewall, private network, floating IP, DNS zone) and expanded to 28 over 14 months. The hardest resources were load balancers (three complete rewrites of state flattening logic due to deep nesting) and Kubernetes clusters (asynchronous 3-5 minute provisioning required polling loops with exponential backoff).

CategoryResourcesCount
ComputeVPS, dedicated server, bare metal, GPU, snapshot, SSH key6
NetworkingFirewall, FW rule, private network, floating IP, reserved IP5
Load BalancingLoad balancer, forwarding rule, backend pool, health check4
DNSZone, record, DNSSEC3
SSLCertificate, Let's Encrypt cert2
KubernetesCluster, node pool, volume, secret4
OtherDomain, project, API token, webhook4

Testing: no mocks

Every resource has acceptance tests against the real Sigilhosting API. Each CI run creates a dedicated account, executes 342 tests covering create, read, update, delete, and import for each resource, then tears down. Full suite: ~45 minutes, ~$12 per run.

Every bug we've shipped in the Terraform provider was in a code path that wasn't covered by an acceptance test running against the real API. Mocks give false confidence.

Import: the underappreciated feature

Import functions are auto-generated from our OpenAPI specification. A code generator reads the API schema for each resource type and produces an importer that fetches and maps every field. When we add attributes, import functions update automatically in the next release.

What's next

Three priorities for 2026: Terraform CDK library (TypeScript, Python, Go instead of HCL), provider-level caching to reduce API calls from ~2,000 to ~400 per plan for 500-resource configs, and drift detection alerts for changes made outside Terraform. The provider is open source at github.com/sigilhosting/terraform-provider-sigilhosting.