GitHub - nelknet/Nelknet.Cdktf: F# Computation Expression Interface for CDKTF

5 min read Original article ↗

Nelknet.Cdktf

NuGet NuGet NuGet NuGet NuGet

F# computation expressions and helpers that sit on top of the CDK for Terraform (CDKTF) .NET bindings. The library is generated directly from a provider's JSII schema so the surface stays in sync with the official SDKs while feeling idiomatic in F# (maps as sequences, arrays as sequences, boolean flags as bool, etc.).

Highlights

Screen.Recording.2025-10-20.at.4.16.40.PM.mov
  • Schema‑driven generation – provider projects carry MSBuild metadata; run dotnet build (or dotnet build -p:ForceCodeGen=true) to refresh src/Providers/<Provider>/Generated/... and commit the regenerated surface alongside your changes.
  • F#‑friendly operations – Terraform maps become seq<string * string>, repeated fields accept seq<'T>, and common unions (e.g., bool | cdktf.IResolvable) expose overloads so you don't have to pass obj.
  • Compile-time required checks – generated builders track required custom operations with phantom types (Missing/Present), so omitting a mandatory field (e.g., name, server_type) produces a compile-time CompilerMessage error instead of a runtime failure.
  • Ambient stack supportstack "name" { ... } keeps the current TerraformStack available without threading it through every builder.

Getting Started

Install the NuGet packages for the providers you need, then create your infrastructure as code in F#.

Prerequisites

  • .NET 8 SDK (dotnet --version ≥ 8.0)
  • Node.js 20.x or 22.x with npm (required by the CDKTF CLI)
  • CDKTF CLI
  • Terraform/OpenTofu CLI
  • Cloud credentials for the providers you plan to use (e.g., HCLOUD_TOKEN for Hetzner, etc.)

Quick Start

  1. Create an infrastructure project

    mkdir Demo.Infra
    cd Demo.Infra
    dotnet new console -lang F#
  2. Install the providers you need

    dotnet add package Nelknet.Cdktf.Providers.Aws      # For AWS
    dotnet add package Nelknet.Cdktf.Providers.Azurerm  # For Azure
    dotnet add package Nelknet.Cdktf.Providers.Hcloud   # For Hetzner

    Provider packages bring Nelknet.Cdktf.Core, the CDKTF runtime, and the generated C# bindings.

  3. Write your infrastructure

    open Nelknet.Cdktf
    open Nelknet.Cdktf.Providers
    open Nelknet.Cdktf.Terraform
    
    [<EntryPoint>]
    let main _ =
        let app =
            stack "demo" {
                let _ =
                    Aws.provider "aws" {
                        region "us-east-1"
                    }
    
                 Aws.s3Bucket "state" {
                     bucket "demo-state-bucket"
                 }
            }
    
        app.Synth()
        0
  4. Add a cdktf.json manifest at the repo root

    {
      "language": "csharp",
      "app": "dotnet run",
      "codeMakerOutput": "generated",
      "terraformProviders": [
        "hashicorp/aws@=5.100.0"
      ],
      "terraformModules": [],
      "context": {}
    }
  5. Deploy your infrastructure

    cdktf synth       # Run the app command and emit Terraform JSON
    cdktf deploy      # Apply the stack (requires provider credentials)

Example: Hetzner Cloud Server

Here's a complete example using the Hetzner Cloud provider:

open Nelknet.Cdktf
open Nelknet.Cdktf.Providers
open Nelknet.Cdktf.Terraform

let apiToken = System.Environment.GetEnvironmentVariable "HCLOUD_TOKEN"

let app =
    stack "hcloud-example" {
        // Register the provider
        let _ =
            Hcloud.provider "hcloud" {
                token apiToken
                poll_interval "750ms"
            }

        // Create a server - schema-driven CE with compile-time checks
        let server =
            Hcloud.server "sample-server" {
                name "fsharp-sample"           // Required field
                server_type "cpx11"            // Required field
                image "ubuntu-22.04"            // Required field
                labels [ "module", "nelknet" ] // Maps as sequences
            }

        // Expose outputs
        Terraform.output "server-name" {
            value server.Name
            description "Expose the created Hetzner server name"
        }
        |> ignore
    }

app.Synth()

Deploy with:

export HCLOUD_TOKEN=... # Your Hetzner API token
cdktf deploy --auto-approve
cdktf destroy --auto-approve   # Clean up when done

Development

Building from Source

After cloning this repository:

dotnet build -p:ForceCodeGen=true

This automatically:

  • Runs the Bootstrap tool (via Directory.Build.targets) which installs Node dependencies if needed and downloads provider bindings
  • Generates F# DSL code for all providers
  • Builds the entire solution

The build process is fully automated - you don't need to run npm install or any other setup commands manually.

Adding a New Provider

Adding a new provider is streamlined with the Bootstrap project:

  1. Ask CDKTF to add the provider

    cdktf provider add hashicorp/random@=3.6.0 --language csharp --force-local
  2. Scaffold any missing provider projects

    dotnet fsi tools/scaffold-providers.fsx
  3. Add the projects to the solution

    dotnet sln add src/Providers/Random/Nelknet.Cdktf.Providers.Random.fsproj
    dotnet sln add generated/random/random.csproj
  4. Run the build

    dotnet build -p:ForceCodeGen=true
  5. Commit everything

    • The updated cdktf.json
    • The new provider's .fsproj file in src/Providers/<Provider>/
    • Any documentation updates
    • All generated changes (both generated/<provider> and src/Providers/<Provider>/Generated/**)

    Push your branch and open a PR. The build workflow simply runs dotnet build, so the PR must include the regenerated artifacts to stay green.

What Happens During the Build

The Bootstrap project (tools/Nelknet.Cdktf.Bootstrap/) automatically:

  • Reads providers from cdktf.json
  • Downloads missing providers using cdktf provider add
  • Normalizes generated C# projects for Central Package Management
  • Creates provider F# projects from the template in src/Providers/_Template/
  • Caches everything to avoid redundant downloads

Upgrading an Existing Provider

To upgrade a provider version:

  1. Update the version in cdktf.json.
  2. Run dotnet build -p:ForceCodeGen=true (or scripts/regenerate.sh) to refresh the generated surface.
  3. Commit the updated cdktf.json together with the regenerated files and open a PR.

Packaging

Pack the core and providers independently:

# Core DSL
dotnet pack src/Core/Nelknet.Cdktf.Core/Nelknet.Cdktf.Core.fsproj -c Release -o artifacts

# Hetzner provider (generated)
dotnet pack src/Providers/Hcloud/Nelknet.Cdktf.Providers.Hcloud.fsproj -c Release -o artifacts

Publish whichever packages you need (dotnet nuget push artifacts/*.nupkg). Consumers can depend on Nelknet.Cdktf.Core plus only the provider packages they require.

The repository also ships a Release workflow (Actions tab) that builds, packs, and publishes every package to NuGet. Set NUGET_API_KEY in your environment or as a repository secret before running it.

Happy infrastructure hacking in F#!