linuxlab.io
Tutorials▾
  • Linux & networking
    File system, processes, TCP/IP, BGP and OSPF
    →
  • Terraform & IaC
    HCL, state, plan/apply on a LocalStack sandbox
    →
  • Git & GitHub
    Object model, plumbing, branching, GitHub Actions
    →
All tutorials →
PricingAboutSign inCreate account
/
Intro
Lessons
Footer
linuxlab-TutorialsPricingAboutPrivacy & cookies
Copyright © 2026 LinuxLab. All rights reserved.
linuxlab.io
Tutorials▾
  • Linux & networking
    File system, processes, TCP/IP, BGP and OSPF
    →
  • Terraform & IaC
    HCL, state, plan/apply on a LocalStack sandbox
    →
  • Git & GitHub
    Object model, plumbing, branching, GitHub Actions
    →
All tutorials →
PricingAboutSign inCreate account
/
  • Введение
  • Уроки
  • How it works
  • База знаний
  • Шпаргалка
  • Capstone
  • Собеседование
home/terraform/lessons/tf-advanced-02-cdktf-typescript

lesson ── terraform-advanced ── ~18 мин ── 7 шагов

CDKTF, Terraform from TypeScript

CDKTF: you write infrastructure in TypeScript, and the output is HCL plus terraform.tfstate. The image already ships cdktf-cli next to node and npm. In this lesson you'll generate a stack, synthesize it, look at the resulting HCL, and deploy.

▶ интерактивный sandbox

Поднимется пара контейнеров: terraform 1.9 и localstack 3.8 в одной сети. В браузере откроется терминал, можно сразу terraform init. Каждый шаг проверяется автоматически. TTL 45 минут, без регистрации.

запустить sandbox →

stack ── terraform · localstack · 1 GB RAM · самоуничтожается через 45 мин простоя

Шаги

  1. 01

    Check cdktf-cli

    bash
    cd /home/student/cdktf-demo
    which cdktf
    cdktf --version
    node --version

    cdktf-cli is baked into the image globally (/usr/local/bin/cdktf), next to node 20 LTS and npm 10. No npm install -g, which saves a minute of install time and removes a runtime dependency on the npm registry.

    ✓ cdktf-cli is in place, the version shows up.

  2. 02

    Initialize a TypeScript project

    bash
    cdktf init --template=typescript --local --project-name=cdktf-demo --project-description="demo" 2>&1 | tail -10
    ls
    cat cdktf.json

    --local keeps state local (not in HCP Terraform). The project now has main.ts, cdktf.json, package.json, and node_modules/.

    cdktf.json is the main config: which language, and which providers to pull in.

    ✓ The TypeScript project is ready.

  3. 03

    Add the AWS provider

    bash
    # edit cdktf.json, add the aws provider
    python3 - <<'EOF'
    import json
    with open('cdktf.json') as f:
        c = json.load(f)
    c['terraformProviders'] = ['aws@~> 5.60']
    with open('cdktf.json', 'w') as f:
        json.dump(c, f, indent=2)
    EOF
    cat cdktf.json
    cdktf get 2>&1 | tail -5
    ls .gen/providers/aws/

    cdktf get generated TypeScript types for the AWS provider. They live in .gen/. Without them, TS does not know about S3Bucket, IamRole, and so on.

    ✓ AWS provider type bindings are ready.

  4. 04

    Write a stack in TypeScript

    bash
    cat > main.ts <<'EOF'
    import { App, TerraformStack, TerraformOutput } from "cdktf";
    import { Construct } from "constructs";
    import { AwsProvider } from "./.gen/providers/aws/provider";
    import { S3Bucket } from "./.gen/providers/aws/s3-bucket";
    class CdktfDemoStack extends TerraformStack {
      constructor(scope: Construct, id: string) {
        super(scope, id);
        new AwsProvider(this, "aws", {
          region: "us-east-1",
          accessKey: "test",
          secretKey: "test",
          s3UsePathStyle: true,
          skipCredentialsValidation: true,
          skipMetadataApiCheck: "true",
          skipRequestingAccountId: true,
          endpoints: [{
            s3:  "http://localstack:4566",
            iam: "http://localstack:4566",
            sts: "http://localstack:4566",
          }],
        });
        const envs = ["dev", "stage", "prod"];
        const buckets = envs.map((env) =>
          new S3Bucket(this, `bucket-${env}`, {
            bucket: `linuxlab-cdktf-${env}`,
            tags: { Environment: env, ManagedBy: "cdktf" },
          })
        );
        new TerraformOutput(this, "bucket-ids", {
          value: buckets.map((b) => b.id),
        });
      }
    }
    const app = new App();
    new CdktfDemoStack(app, "demo");
    app.synth();
    EOF

    Three buckets through Array.map, and that is the advantage of CDKTF over native HCL. In HCL you write for_each = toset(...); in TS, a plain loop with types.

    ✓ The TypeScript stack is ready. Now we synthesize.

  5. 05

    cdktf synth, look at the generated HCL

    bash
    cdktf synth 2>&1 | tail -5
    cat cdktf.out/stacks/demo/cdk.tf.json | jq '.resource.aws_s3_bucket | keys'

    You see three keys: bucket-dev, bucket-stage, bucket-prod. This is the plain JSON form of HCL, and Terraform understands it.

    ✓ HCL generated. 3 buckets from a single array.map.

  6. 06

    cdktf deploy

    bash
    cdktf deploy demo --auto-approve 2>&1 | tail -10
    aws --endpoint-url=http://localstack:4566 s3 ls

    You see three buckets created. Under the hood, cdktf deploy = synth + terraform init + terraform apply in cdktf.out/stacks/demo/.

    State is a plain terraform.tfstate in the same folder. This means you can drop CDKTF, delete main.ts, and write HCL by hand: the state is compatible, and you lose nothing.

    ✓ Three buckets deployed through TypeScript.

    The same thing on OpenTofu

    OpenTofu keeps the CLI and state compatible with Terraform for the commands in this step: migration usually goes through mv .terraform .terraform.bak; tofu init -upgrade. But on the first switch, back up the state and run on a feature branch, since the differences concentrate in the newer features (variables in the backend, state encryption, OCI registry-backed modules). See tf-opentofu-parity for the full matrix.

    • → OpenTofu parity
  7. 07

    When CDKTF is justified

    When native HCL is shorter, it is the choice:

    hcl
    # HCL, 5 lines
    resource "aws_s3_bucket" "envs" {
      for_each = toset(["dev", "stage", "prod"])
      bucket   = "linuxlab-${each.key}"
    }

    When generation gets more complex, CDKTF wins:

    typescript
    // 100 tenants from JSON, different conf per tenant
    const tenants = require("./tenants.json");
    tenants.forEach((t: any) => {
      if (t.tier === "premium") {
        new PremiumTenantStack(app, `tenant-${t.id}`, t);
      } else {
        new BasicTenantStack(app, `tenant-${t.id}`, t);
      }
    });

    In HCL this would take 3-4 modules and hairy count = condition ? 1 : 0 logic. In TS, it is linear.

    Remember the smell:

    • Fewer than 50 resources, simple infra → native HCL.
    • Programmable generation (forEach, if, classes) → CDKTF.
    • The team does not know TypeScript → definitely native HCL.

    Cleanup:

    bash
    cdktf destroy demo --auto-approve 2>&1 | tail -3

    ✓ CDKTF used deliberately. Next, your own provider in Go.

    Custom Construct, reuse without modules

    The CDKTF equivalent of a Terraform module is a Construct:

    typescript
    import { Construct } from "constructs";
    import { S3Bucket } from "./.gen/providers/aws/s3-bucket";
    import { S3BucketVersioningA } from "./.gen/providers/aws/s3-bucket-versioning";
    interface AuditedBucketProps {
      name: string;
      tags?: { [key: string]: string };
    }
    export class AuditedBucket extends Construct {
      public readonly bucket: S3Bucket;
      constructor(scope: Construct, id: string, props: AuditedBucketProps) {
        super(scope, id);
        this.bucket = new S3Bucket(this, "bucket", {
          bucket: props.name,
          tags: props.tags,
        });
        new S3BucketVersioningA(this, "versioning", {
          bucket: this.bucket.id,
          versioningConfiguration: { status: "Enabled" },
        });
      }
    }
    // usage
    new AuditedBucket(this, "logs", { name: "linuxlab-logs" });

    Advantages over a Terraform module: type-safe props, inheritance (extends), and composition through TypeScript.

    • → CDKTF in full

Что ты узнал

cdktf is already in the image. cdktf init --template=typescript gives you a skeleton. main.ts holds App + Stack + Constructs. cdktf get pulls type bindings for providers. cdktf synth generates cdktf.out/stacks/<name>/cdk.tf.json. cdktf deploy runs apply.

команды

  • cdktf --versioncdktf-cli is preinstalled in the sandbox image.
  • cdktf init --template=typescript --localproject skeleton.
  • cdktf getpull type bindings for providers.
  • cdktf synthgenerate HCL.
  • cdktf deploy <stack>synth + apply in one command.

концепции

  • · Class extends TerraformStack, one stack = one root Terraform configuration
  • · Construct, a reusable block, the equivalent of a Terraform module
  • · cdktf.out/, generated, gitignore

← предыдущий

Linters: fmt, validate, tflint

следующий →

Variables: removing the hardcode

Footer
linuxlab-
Copyright © 2026 LinuxLab. All rights reserved.
Tutorials
Pricing
About
Privacy & cookies