• HashiCorp Developer

  • HashiCorp Cloud Platform
  • Terraform
  • Packer
  • Consul
  • Vault
  • Boundary
  • Nomad
  • Waypoint
  • Vagrant
Terraform
  • Install
  • Tutorials
    • About the Docs
    • Configuration Language
    • Terraform CLI
    • Terraform Cloud
    • Terraform Enterprise
    • CDK for Terraform
    • Provider Use
    • Plugin Development
    • Registry Publishing
    • Integration Program
  • Registry(opens in new tab)
  • Try Cloud(opens in new tab)
  • Sign up
Terraform Home

CDK for Terraform

Skip to main content
  • CDK for Terraform
  • Get Started
    • Architecture
    • HCL Interoperability
    • Constructs
    • Providers
    • Resources
    • Modules
    • Data Sources
    • Variables and Outputs
    • Functions
    • Iterators
    • Remote Backends
    • Aspects
    • Assets
    • Tokens
    • Stacks
  • Community
  • Telemetry

  • Resources

  • Tutorial Library
  • Certifications
  • Community Forum
    (opens in new tab)
  • Support
    (opens in new tab)
  • GitHub
    (opens in new tab)
  • Terraform Registry
    (opens in new tab)
  1. Developer
  2. Terraform
  3. CDK for Terraform
  4. Concepts
  5. Constructs
  • CDK For Terraform
  • v0.14.x
  • v0.13.x
  • v0.12.x
  • v0.11.x
  • v0.10.x
  • v0.9.x
  • v0.8.x

»Constructs

Construct is a general term that you can use to describe parts of your CDK for Terraform (CDKTF) project because every element in a CDKTF application is a descendent of the Construct base class. The entire application, each stack, and each resource are all constructs.

You can also import custom construct classes that represent infrastructure configurations written in your programming language. Custom constructs are helpful because they enforce configuration best practices, they let you reuse configurations rather than rewriting them multiple times, and they abstract some configuration details away. For example, you might import a construct that configures a Kubernetes deployment. When you create a new instance in your application, you can customize the deployment via the exposed properties without learning all of the implementation details.

Construct Types

Construct classes define system state at various levels of granularity. For example, you can write a custom construct that defines and configures a single Elastic Cloud Compute resource or one that defines and configures an entire deployment with multiple resources from different providers.

The Cloud Development Kit community has identified three major construct types that indicate an increasing level of abstraction:

  • L1 constructs define single resources or very small units of configuration. For example, the code bindings that CDKTF generates for each Terraform provider are considered L1 constructs. Another example would be creating a custom L1 construct that defines the configuration for an Azure virtual machine.

  • L2 constructs define resources and include an intent-driven API with additional helper methods, properties, and functionality. For example, you could create a custom L2 construct that contains a method for adding files to an S3 Bucket and a method for granting resource access to a particular user group.

  • L3 constructs define common design patterns and larger pieces of functionality. For example, you could create a custom L3 construct that configures all of the necessary resources to deploy and host a static website frontend.

Modules vs. Constructs

Both Terraform modules and constructs allow you to reuse configurations and customize those configurations via exposed properties. However, constructs let you compose more complex objects while maintaining type safety because they can use the built-in functionality from your preferred programming language.

Interoperability

You can use the cdktf convert command to translate existing projects written in HashiCorp Configuration Language (HCL) into CDKTF-compatible projects. You could then use the output as a starting point to create custom constructs.

You cannot create a Terraform module directly from an existing CDKTF construct, but you can use the synthesized output of a CDKTF project as a Terraform Module. Refer to the HCL interoperability page for more details.

Constructs vs. Stacks

Stacks represent a collection of infrastructure that CDK for Terraform (CDKTF) synthesizes as a dedicated Terraform configuration. Stacks allow you to separate the state management for multiple environments within an application. For example, you may want to have one stack that describes the infrastructure for development and another with slightly different inputs for testing.

Constructs also provide a way to logically structure a set of resources, but you can only use them as part of a stack. A single stack may contain multiple constructs that act as building blocks to create the full infrastructure configuration. For example, you could use one construct to define a Kubernetes deployment and a different construct to define an AWS DynamoDB table.

Use Constructs

Hands On: Try Deploy Applications with CDK for Terraform tutorial to use a custom construct. It includes the example code below.

You can import any CDKTF-compatible construct that is available in your project's programming language. Then, you can create new instances of the construct and use any exposed properties to customize the construct configuration.

The following example instantiates a construct called KubernetesWebAppDeployment and uses the available arguments to specify that the deployment will have two replicas.

import { Construct } from "constructs";
import { TerraformStack } from "cdktf";
import { KubernetesWebAppDeployment } from "./constructs/kubernetes-web-app-deployment";
import { KubernetesProvider } from "./.gen/providers/kubernetes/provider";
import * as path from "path";

export class ConstructsStack extends TerraformStack {
  constructor(scope: Construct, name: string) {
    super(scope, name);

    new KubernetesProvider(this, "kind", {
      configPath: path.join(__dirname, "../kubeconfig.yaml"),
    });

    new KubernetesWebAppDeployment(this, "deployment", {
      image: "nginx:latest",
      replicas: 2,
      app: "myapp",
      component: "frontend",
      environment: "dev",
    });
  }
}
class MyKubernetesStack(TerraformStack):
    def __init__(self, scope: Construct, name: str):
        super().__init__(scope, name)
        KubernetesProvider(self, "kind",
            config_path=os.path.join(os.path.dirname(__file__), '..', 'kubeconfig.yaml')
        )

        KubernetesWebAppDeployment(self, "deployment",
            image="nginx:latest",
            replicas=2,
            app="myapp",
            component="frontend",
            environment="dev"
        )



app = App()
MyKubernetesStack(app, "demo")
app.synth()
import java.nio.file.Paths;
import imports.kubernetes.provider.KubernetesProvider;
import imports.kubernetes.provider.KubernetesProviderConfig;
import com.mycompany.app.myconstructs.KubernetesWebAppDeployment;
import com.mycompany.app.myconstructs.KubernetesWebAppDeploymentConfig;


public class MainUseConstructs extends TerraformStack {

    public MainUseConstructs(Construct scope, String name){
        super(scope, name);

        new KubernetesProvider(this, "kind", KubernetesProviderConfig.builder()
                .configPath(Paths.get(System.getProperty("user.dir"), "..", "kubeconfig.yaml").toString())
                .build()
        );

        new KubernetesWebAppDeployment(this, "deployment",  KubernetesWebAppDeploymentConfig.builder()
                .image("nginx:latest")
                .replicas(2)
                .app("myapp")
                .components("frontend")
                .environments("dev")
                .build()
        );
    }

    public static void main(String[] args) {
        final App app = new App();
        new MainUseConstructs(app, "demo");
        app.synth();
    }
}
using System;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using Constructs;
using HashiCorp.Cdktf;
using kubernetes.Provider;
using MyConstructs;

namespace Examples
{
    class MyConstructsStack : TerraformStack
    {
        public MyConstructsStack(Construct scope, string name) : base(scope, name)
        {
            new KubernetesProvider(this, "kind", new KubernetesProviderConfig {
                ConfigPath = Path.Join(Environment.CurrentDirectory, "../kubeconfig.yaml")
            });
            new KubernetesWebAppDeployment(this, "deployment", new Dictionary<string, object> {
                { "image", "nginx:latest" },
                { "replicas", 2 },
                { "app", "myapp" },
                { "component", "frontend" },
                { "environment", "dev" }
            });
        }
    }
}
package main

import (
    kubernetes "github.com/hashicorp/terraform-cdk/examples/go/documentation/generated/hashicorp/kubernetes/provider"
    "github.com/hashicorp/terraform-cdk/examples/go/documentation/myconstructs"
    "github.com/aws/constructs-go/constructs/v10"
    "github.com/aws/jsii-runtime-go"
    "github.com/hashicorp/terraform-cdk-go/cdktf"

    "os"
    "path"
)

func NewExampleCdktfDocumentationStack(scope constructs.Construct, name string) cdktf.TerraformStack {
    stack := cdktf.NewTerraformStack(scope, &name)

    cwd, _ := os.Getwd()

    kubernetes.NewKubernetesProvider(stack, jsii.String("kind"), &kubernetes.KubernetesProviderConfig{
        ConfigPath: jsii.String(path.Join(cwd, "kubeconfig.yaml")),
    })
    myconstructs.NewKubernetesWebAppDeployment(stack, "deployment", map[string]interface{}{
        "image":       jsii.String("nginx:latest"),
        "replicas":    jsii.Number(2),
        "app":         jsii.String("myapp"),
        "component":   jsii.String("frontend"),
        "environment": jsii.String("dev"),
    })

    return stack
}

func main() {
    app := cdktf.NewApp(nil)

    NewExampleCdktfDocumentationStack(app, "demo")

    app.Synth()
}

Scope

You can instantiate a construct multiple times throughout your infrastructure. For example, you may want to create multiple S3 Buckets with different configurations. CDKTF infers a unique name for each instance from its Construct#id and parent construct. Instances that share the same parent element are considered to be part of the same scope. If you instantiate multiple constructs within the same scope, you must set a different name for each instance to avoid naming conflicts.

The following example creates three different S3 buckets, two of which are in the same scope. When CDKTF synthesizes this configuration, the Terraform IDs for these resources will be the construct names prefixed by the stack name and suffixed with a hash for each Construct instance.

import { Construct } from "constructs";
import { TerraformStack } from "cdktf";

class PublicS3Bucket extends Construct {
  public bucket: S3Bucket;
  constructor(scope: Construct, name: string) {
    super(scope, name); // This creates a new scope since we extend from construct

    // This bucket is in a different scope than the buckets
    // defined in `MyStack`. Therefore, it does not need a unique name.
    this.bucket = new S3Bucket(this, "bucket", {
      bucketPrefix: name,
      website: [
        {
          indexDocument: "index.html",
          errorDocument: "5xx.html",
        },
      ],
    });
  }
}

class MyStack extends TerraformStack {
  constructor(scope: Construct, name: string) {
    super(scope, name);

    // Both buckets are inside of the same stack, meaning they share
    // the same scope. Therefore, their names must be unique.
    new PublicS3Bucket(this, "first-bucket");
    new PublicS3Bucket(this, "second-bucket");
  }
}
from constructs import Construct
from cdktf import App, TerraformStack

class PublicS3Bucket(Construct):

    bucket: S3Bucket

    def __init__(self, scope: Construct, name: str):
        # This creates a new scope since we extend from construct
        super().__init__(scope, name)

        AwsProvider(self, "aws",
            region="us-east-1"
        )

        # This bucket is in a different scope than the buckets
        # defined in `MyStack`. Therefore, it does not need a unique name.
        self.bucket = S3Bucket(self, "bucket",
                        bucket_prefix=name,
                        website=S3BucketWebsite(
                            index_document="index.html",
                            error_document="5xx.html",
                        )
                    )

class MyS3BucketStack(TerraformStack):
    def __init__(self, scope: Construct, name: str):
        super().__init__(scope, name)

        # Both buckets are inside of the same stack, meaning they share
        # the same scope. Therefore, their names must be unique.
        PublicS3Bucket(self, "first-bucket")
        PublicS3Bucket(self, "second-bucket")

app = App()
MyS3BucketStack(app, "s3-stack")
app.synth()
import software.constructs.Construct;
import com.hashicorp.cdktf.TerraformStack;
import com.hashicorp.cdktf.App;
public class MainConstructScope extends TerraformStack {

    public MainConstructScope(Construct scope, String name){
        super(scope, name);

        new MainConstructScope.PublicS3Bucket(this, "first-bucket");
        new MainConstructScope.PublicS3Bucket(this, "second-bucket");

    }

    static class PublicS3Bucket extends Construct{

        public S3Bucket bucket;

        public PublicS3Bucket(Construct scope, String name){
            super(scope, name);

            new AwsProvider(this, "aws", AwsProviderConfig.builder()
                    .region("us-east-1")
                    .build()
            );

            this.bucket = new S3Bucket(this, "bucket", S3BucketConfig.builder()
                    .bucketPrefix(name)
                    .website(S3BucketWebsite.builder()
                            .indexDocument("index.html")
                            .errorDocument("5xx.html")
                            .build()
                    )
                    .build()
            );
        }
    }
}

Aspects

Aspects allow you to implement a visitor pattern, dynamically add or remove constructs, and automatically change attributes of existing constructs when you synthesize your CDTKF application. For example, you could use an aspect to help tag resources based on dynamic conditions. Refer to the aspects documentation for more details.

Available Constructs

You can search the Amazon Web Services (AWS) Construct Hub for existing CDKTF-compatible constructs. We are also building the AWS Adapter, which lets you use AWS Constructs in your CDKTF projects.

Note: The AWS Adapter is in tech preview.

Write and Publish Constructs

You can write construct classes in any language and distribute them to other users. Refer to Construct Design and Construct Publishing and Distribution for details.

Edit this page on GitHub

On this page

  1. Constructs
  2. Construct Types
  3. Modules vs. Constructs
  4. Constructs vs. Stacks
  5. Use Constructs
  6. Write and Publish Constructs
Give Feedback(opens in new tab)
  • Certifications
  • System Status
  • Terms of Use
  • Security
  • Privacy
  • Trademark Policy
  • Trade Controls
  • Give Feedback(opens in new tab)