- /
- channels
- /
- brads-forge
- /
- cdkts
Install with Pixi
pixi add cdkts Install with conda
conda install -c https://prefix.dev/brads-forge cdkts Description
🚀 CDKTS - CDK for Terraform/OpenTofu
A modern alternative to HashiCorp's deprecated CDK-TF library, built on top of Deno.
Why CDKTS?
HashiCorp's terraform-cdk (CDK-TF) has been deprecated, leaving a gap for developers who want to define infrastructure using TypeScript instead of HCL. CDKTS fills this gap with a modern, streamlined approach that leverages Deno's capabilities and provides:
- 🎯 True TypeScript-first experience - No generated bindings required
- 🚀 Built for Deno - Modern runtime, built-in TypeScript, secure by default
- 📦 Zero Node.js dependencies - No npm install, no node_modules
- 🔧 Direct HCL synthesis - Clean, readable Terraform/OpenTofu configurations
- 🌐 Native provider support - Work with any Terraform provider
- 🔥 Deno Bridge Provider - Write custom providers in TypeScript, not Go
- 📦 Standalone compilation - Bundle stacks into single executables
- 🎨 Type-safe inputs/outputs - Catch errors at compile time
Features
Core Capabilities
- Type-Safe Stack Definition: Define stacks with compile-time type safety for inputs and outputs
- HCL Synthesis: Generate clean, readable Terraform/OpenTofu configurations
- Automation API: Programmatically init, plan, apply, and destroy infrastructure
- CLI Tool: Familiar terraform/opentofu-like CLI experience with TypeScript stacks
- Multiple Stack Support: Coordinate multiple stacks with dependencies
- Binary Management: Automatically download and cache Terraform/OpenTofu binaries
Advanced Features
- Inline Stacks: Execute stacks directly without separate files
- Bundled Stacks: Compile stacks to standalone executables (includes binaries!)
- Deno Bridge Provider: Implement provider logic in TypeScript using the terraform-provider-denobridge
- Custom Resources: Define resources with full CRUD lifecycle in TypeScript
- Custom Data Sources: Implement data sources in TypeScript
- Custom Actions: Run TypeScript code during terraform lifecycle events
- Ephemeral Resources: Support for temporary resources during operations
Installation
Using JSR Package
# Add to your deno.json imports
deno add jsr:@brad-jones/cdkts
// And import using the import map
import { Stack } from "@brad-jones/cdkts/constructs";
import { Project } from "@brad-jones/cdkts/automate";
// Or import directly without any deno config
import { Stack } from "jsr:@brad-jones/cdkts/constructs";
import { Project } from "jsr:@brad-jones/cdkts/automate";
Using the CLI
You have at least 3 ways to invoke the CLI using deno
-
Using Deno Install (Recommended):
deno install --global -A -f jsr:@brad-jones/cdkts/cli
Then just call the installed command like:cdkts apply ./my_stack
see: https://docs.deno.com/runtime/reference/cli/install/ -
Using the new DX Alias (Deno's version of npx):
deno x jsr:@brad-jones/cdkts/clior justdx jsr:@brad-jones/cdkts/cli
see: https://deno.com/blog/v2.6 -
And of course good old Deno Run:
deno run -A jsr:@brad-jones/cdkts/cli
see: https://docs.deno.com/runtime/reference/cli/run/
Compiled Binary
The CLI is also packaged as a statically compiled binary, that includes an embedded copy of the deno runtime. Consider this to be experimental!
Download direct from: https://github.com/brad-jones/cdkts/releases
Pixi
Or install with pixi.
Add my channel: https://prefix.dev/channels/brads-forge/packages/cdkts
# Add just to your current pixi workspace
pixi workspace channel add https://prefix.dev/brads-forge
# Or add to your global config
pixi --global config append default-channels https://prefix.dev/brads-forge
# Then install
pixi add cdkts
Quick Start
1. Create Your First Stack
Create my_stack.ts:
import { Resource, Stack, Terraform } from "@brad-jones/cdkts/constructs";
export default class MyStack extends Stack<typeof MyStack> {
constructor() {
super(`${import.meta.url}#${MyStack.name}`);
new Terraform(this, {
requiredVersion: ">=1,<2.0",
requiredProviders: {
local: {
source: "hashicorp/local",
version: "2.6.1",
},
},
});
new Resource(this, "local_file", "hello", {
filename: "${path.module}/message.txt",
content: "Hello World",
});
}
}
2. Apply Your Stack
cdkts apply ./my_stack.ts
That's it! CDKTS will:
- Synthesize your TypeScript to HCL
- Download OpenTofu (or Terraform)
- Initialize the project
- Apply the configuration
Core Concepts
Stack
A Stack is the fundamental unit in CDKTS. It represents a complete Terraform/OpenTofu configuration:
export default class MyStack extends Stack<typeof MyStack> {
static override readonly Props = class extends Stack.Props {
// Define inputs
filename = new Stack.Input();
// Define outputs
contentHash = new Stack.Output();
};
constructor() {
super(`${import.meta.url}#${MyStack.name}`);
// Add resources, providers, etc.
const file = new Resource(this, "local_file", "hello", {
filename: this.inputs.filename,
content: "Hello World",
});
this.outputs = {
contentHash: file.outputs.content_sha256,
};
}
}
Type-Safe Inputs and Outputs
CDKTS provides compile-time type safety for stack inputs and outputs:
static override readonly Props = class extends Stack.Props {
// String input (default type)
filename = new Stack.Input();
// Typed input
port = new Stack.Input<number>({ default: 8080 });
// Optional input with description
region = new Stack.Input({
default: "us-west-2",
description: "AWS region"
});
// Outputs
instanceId = new Stack.Output<string>({
description: "The EC2 instance ID",
sensitive: true
});
};
Resources
Define any Terraform resource using the generic Resource construct:
new Resource(this, "aws_instance", "web", {
ami: "ami-12345678",
instance_type: "t3.micro",
tags: {
Name: "WebServer",
},
});
Project (Automation API)
The Project class provides programmatic control over the Terraform/OpenTofu lifecycle:
import { Project } from "@brad-jones/cdkts/automate";
import MyStack from "./my_stack.ts";
const project = new Project({
stack: new MyStack(),
});
// Initialize
await project.init();
// Plan
const plan = await project.plan();
// Apply
const state = await project.apply(plan);
console.log(state.values?.outputs);
// Destroy
await project.destroy();
Examples
The examples/ directory contains progressively more advanced examples:
- Hello World - Basic stack with a single resource
- Inputs and Outputs - Type-safe parameterization
- Deno Bridge Provider - Custom providers in TypeScript
- Inline Stack - Execute without separate stack files
- Bundled Stack - Compile to standalone executable
- Multiple Stacks - Coordinate dependent stacks
CLI Usage
CDKTS provides a CLI that wraps Terraform/OpenTofu with TypeScript stack support:
Common Commands
# Initialize a stack
cdkts init ./my_stack.ts
# Plan changes
cdkts plan ./my_stack.ts
# Apply configuration
cdkts apply ./my_stack.ts
# Destroy infrastructure
cdkts destroy ./my_stack.ts
# Validate configuration
cdkts validate ./my_stack.ts
# View outputs
cdkts output ./my_stack.ts
Pass-Through Arguments
All commands support pass-through arguments using --:
# Pass flags to terraform/opentofu
cdkts plan ./my_stack.ts -- -var="instance_type=t3.micro" -out=tfplan
cdkts apply ./my_stack.ts -- -auto-approve -parallelism=10
Environment Variables
Configure CDKTS behavior via environment variables:
# Use Terraform instead of OpenTofu
export CDKTS_FLAVOR=terraform
# Use a specific binary
export CDKTS_TF_BINARY_PATH=/usr/local/bin/terraform
# Specify version to download
export CDKTS_TF_VERSION=1.11.4
# Set project directory
export CDKTS_PROJECT_DIR=./build/terraform
Escape Hatch
Execute any Terraform/OpenTofu command not explicitly wrapped:
cdkts show ./my_stack.ts
cdkts state list ./my_stack.ts
cdkts console ./my_stack.ts
Advanced Usage
Multiple Stacks with Dependencies
Coordinate multiple stacks and pass outputs between them:
import { Project } from "@brad-jones/cdkts/automate";
import Stack1 from "./stack_1.ts";
import Stack2 from "./stack_2.ts";
// Apply first stack
const state1 = await new Project({ stack: new Stack1() }).apply();
// Pass outputs to second stack
await new Project({
stack: new Stack2({
dbEndpoint: state1.values!.outputs!.dbEndpoint.value,
}),
}).apply();
Bundled Stacks (Standalone Executables)
Compile your stack into a single executable that includes everything:
import { StackBundler } from "@brad-jones/cdkts/automate";
await new StackBundler({
stackFilePath: "./my_stack.ts",
outputPath: "./dist/my_stack",
targets: ["linux-x86_64", "darwin-aarch64", "windows-x86_64"],
tfVersion: "1.11.4",
flavor: "tofu",
}).bundle();
This creates executables that:
- Include the compiled TypeScript stack
- Embed the Terraform/OpenTofu binary
- Embed the .terraform.lock.hcl lock file
- As well as the providers that match the lock file
- Requiring no external dependencies
- Can be shipped as single files
Also available via the CLI @ cdkts bundle
Deno Bridge Provider
https://github.com/brad-jones/terraform-provider-denobridge
Write custom Terraform providers in TypeScript:
import { DenoResource } from "@brad-jones/cdkts/constructs";
import { ZodResourceProvider } from "@brad-jones/terraform-provider-denobridge";
import { z } from "@zod/zod";
// >>> Define your type safe schemas (Zod is optional but recommend)
// -----------------------------------------------------------------
const Props = z.object({
path: z.string(),
content: z.string(),
});
const State = z.object({
mtime: z.number(),
});
// >>> Define the Construct class that you use in your Stack
// -----------------------------------------------------------------
export class FileResource extends DenoResource<typeof FileResource> {
static override readonly Props = class extends DenoResource.Props {
override props = new DenoResource.ZodInput(Props);
override state = new DenoResource.ZodOutput(State);
};
constructor(parent: Construct, label: string, props: z.infer<typeof Props>) {
super(parent, label, {
props,
path: import.meta.url, // <<<--- This is the key, it tells the denobridge provider to run this same script.
permissions: { all: true },
});
}
}
// >>> Define the Provider for the Construct (runs via JSON-RPC)
// -----------------------------------------------------------------
if (import.meta.main) {
new ZodResourceProvider(Props, State, {
async create({ path, content }) {
await Deno.writeTextFile(path, content);
return {
id: path,
state: { mtime: (await Deno.stat(path)).mtime!.getTime() },
};
},
async read({ id }) {
const stat = await Deno.stat(id);
return { state: { mtime: stat.mtime!.getTime() } };
},
async update({ id, props: { content } }) {
await Deno.writeTextFile(id, content);
return { state: { mtime: (await Deno.stat(id)).mtime!.getTime() } };
},
async delete({ id }) {
await Deno.remove(id);
},
});
}
See the Deno Bridge example for complete examples of resources, data sources, actions, and ephemeral resources.
API Overview
Constructs
Construct: Base class for all constructsStack: Base class for defining stacks. Is the "root" Construct.Block: Extends the Construct to express any valid HCL blockTerraform: Extends Block, to provide the Terraform settings blockBackend: Extends Block, to provide a genericbackendblockLocalBackend: Extends Backend, to provide a type safe local backend blockRemoteBackend: Extends Backend, to provide a type safe remote backend block- TODO: remaining backends but you can still express any backend with the base Backend block.
Provider: Extends Block, to provide a genericproviderblockDenoBridgeProvider: Extends Provider, to provide a type safedenobridgeprovider block.
Action- Extends Block, to provide a genericactionblockDenoAction: Extends Action, to provide a genericdenobridge_actionblock.
Resource- Extends Block, to provide a genericresourceblockDenoResource: Extends Resource, to provide a genericdenobridge_resourceblock.
EphemeralResource- Extends Block, to provide a genericephemeralblockDenoEphemeralResource: Extends Resource, to provide a genericdenobridge_resourceblock.
DataSource- Extends Block, to provide a genericdatasourceblockDenoDataSource: Extends DataSource, to provide a genericdenobridge_datasourceblock.
Variable- Input variableOutput- Output valueModule- TODOLocals- TODOCheck- TODOImport- TODOMoved- TODORemoved- TODO
Remember any block that is currently not implemented can still be expressed directly with the Block class!
Automation
Project- Lifecycle management (init, plan, apply, destroy)StackBundler- Compile stacks to executables