Been managing infra with Terraform HCL for years. Got sick of the endless copy-paste and the sheer bulk of modules for anything moderately complex. Vendor docs are a maze. Moved a core production network stack to CDKTF with Go as the language. Here's the raw difference.
**Before: Terraform HCL (modules, variables, ugly joins)**
Creating a GCP VPC with some subnets and a router. Typical module boilerplate.
```hcl
module "vpc" {
source = "terraform-google-modules/network/google"
version = "~> 9.0"
project_id = var.project_id
network_name = "prod-vpc"
subnets = [
{
subnet_name = "subnet-a"
subnet_ip = "10.10.0.0/24"
subnet_region = "us-central1"
}
]
routes = [
{
name = "egress-internet"
destination_range = "0.0.0.0/0"
next_hop_internet = "true"
}
]
}
resource "google_compute_router" "cloud_router" {
name = "prod-router"
network = module.vpc.network_name
region = "us-central1"
}
```
You know the drill. Need another subnet? Update the list. Need programmatic logic? Good luck with `for` expressions or bringing in another external module.
**After: CDKTF Go**
Same infrastructure. Now it's just Go code.
```go
package main
import (
"github.com/aws/constructs-go/constructs/v10"
"github.com/hashicorp/terraform-cdk-go/cdktf"
"github.com/cdktf/cdktf-provider-google-go/google/v8/computeNetwork"
"github.com/cdktf/cdktf-provider-google-go/google/v8/computeSubnetwork"
"github.com/cdktf/cdktf-provider-google-go/google/v8/computeRouter"
)
func NewProdNetworkStack(scope constructs.Construct, id string) cdktf.TerraformStack {
stack := cdktf.NewTerraformStack(scope, &cdktf.TerraformStackConfig{
Context: &cdktf.TerraformStackContext{Namespace: id},
})
// Config from env or variables - it's just Go.
project := "my-project"
network := computeNetwork.NewComputeNetwork(stack, jsii.String("prod_vpc"), &computeNetwork.ComputeNetworkConfig{
Name: jsii.String("prod-vpc"),
AutoCreateSubnetworks: jsii.Bool(false),
Project: &project,
})
subnetA := computeSubnetwork.NewComputeSubnetwork(stack, jsii.String("subnet_a"), &computeSubnetwork.ComputeSubnetworkConfig{
Name: jsii.String("subnet-a"),
IpCidrRange: jsii.String("10.10.0.0/24"),
Region: jsii.String("us-central1"),
Network: network.Id(),
Project: &project,
})
_ = computeRouter.NewComputeRouter(stack, jsii.String("cloud_router"), &computeRouter.ComputeRouterConfig{
Name: jsii.String("prod-router"),
Network: network.Name(),
Region: subnetA.Region(),
Project: &project,
})
return stack
}
```
**The payoff:**
- **Logic is code.** Need to create subnets based on a list? It's a `for` loop. Need to fetch data from an API before defining resources? Make a function.
- **No more HCL functions.** String manipulation, conditionals, loops - use the full language.
- **Refactoring.** Rename a variable? Your IDE does it. Extract common patterns into functions or structs.
- **Type safety.** Compiler catches a lot of dumb mistakes before `plan`.
**The cost:**
- **State migration.** Used `terraform state rm` and `terraform import` for each resource. Took a day for this stack. Tedious but one-time.
- **CDKTF is another layer.** Sometimes you fight the CDKTF abstractions. Docs are thinner.
- **Team buy-in.** Need developers comfortable with Go, not just HCL.
**Worth it?** For us, yes. The stack is more maintainable. New features are added faster. The initial migration pain was a solid investment. Would only recommend if your team already knows and wants to use a real programming language. If not, you're just adding complexity.
slow pipelines make me cranky