Preface
Over the last 3 years I've started numerous IAC projects that utilized CDKs heavily -
- AWS CDK infrastructure and CloudFormation template generation
-
cdktf
for Terraform IAC -
cdk8s
with ArgoCD for Kubernetes
The main reason I like this stack so much, is the type-safety and easier learning curve that it provides.
New people can easily navigate in the project, with the best intellisense and type safety that TypeScript can provide.
While I really recommend using these tools, it's an emerging technology and hard to migrate to. If you're starting a new project, or a new company, I'd highly recommend you consider them.
I'll cover these topics more thoroughly in future posts, but for now I'll focus on the configuration library that I've been developing over the last few years.
π‘
I've met, and talked to many DevOps engineers who are against using anything other than plain Terraform and Helm. I've converted some of them to the dark side
But as they say - Different strokes for different folks.
@cdklib/config
Have you ever struggled with managing configuration across your dev, staging, and prod environments? You're not alone. While tools like Terragrunt (for Terraform) and Helm (for Kubernetes) handle this beautifully, we've been missing something similar in the CDK world.
That's why I built @cdklib/config
- a simple, type-safe configuration library for CDK projects. Whether you're using AWS CDK, cdktf, or cdk8s, this tool can help bring some sanity to your infrastructure code.
The Configuration Headache in CDK
If you've worked on CDK projects with multiple environments, you've probably faced these issues:
- Values hard-coded all over your code
- Messy if/else statements for different environments
- No type checking for your config values
- Each team member handling config differently
These issues might not matter much in small projects, but they become real headaches as things grow.
Another issue you may encounter, is having to copy a bunch of role ARNs to your Kubernetes / Helm charts.
How @cdklib/config Helps
This library brings a simple approach to configuration with several key benefits:
- Type safety - get TypeScript help with your configuration (using Zod for validation)
- Nested environments - organize configs like dev β dev/staging β dev/east/staging
- Calculated values - compute values based on environment and other settings
- Modular design - organize config logically for your needs
- Easy CDK integration - works with the CDK context system you already use
Getting Started
Installing is simple:
npm install @cdklib/config
Customizing Environment IDs (Recommended)
By default, an environment ID is any string. This is undesirable, since it's prone to typos and mistakes.
For better type safety, you can define your own environment IDs in a .d.ts
file:
// cdklib-config.d.ts
declare module "@cdklib/config/types" {
export type EnvId = "global" | "dev/staging" | "dev/qa" | "prod/us-central-1";
}
Then add this file to your tsconfig.json
:
{
"include": ["...", "path/to/cdklib-config.d.ts"]
}
This gives you autocompletion for your environments when using set
and get
methods.
Basic Example
Here's how to configure AWS account info across environments:
import { CdkConfig } from "@cdklib/config";
import { z } from "zod";
// Define your configuration schema
const awsSchema = z.object({
accountId: z.string(),
region: z.string(),
tags: z.record(z.string()).optional(),
});
// Create and configure
export const awsConfig = new CdkConfig(awsSchema)
.setDefault({
tags: { ManagedBy: "CDK" },
})
.set("dev", {
accountId: "123456789012",
region: "us-east-1",
tags: { Environment: "development" },
})
.set("prod", {
accountId: "987654321098",
region: "us-west-2",
tags: { Environment: "production" },
});
// Get configuration for a specific environment
const devConfig = awsConfig.get("dev/staging");
console.log(devConfig);
// {
// accountId: '123456789012',
// region: 'us-east-1',
// tags: { ManagedBy: 'CDK', Environment: 'development' }
// }
What I love about this approach:
- Your configuration is type-safe - TypeScript helps you include all the required fields
- It's validated when you run it - clear errors if you're missing something important, before you start applying your infrastructure
- It's all in one place - no more hunting through code for environment settings
Building Nested Environments
As projects grow, you often need more detailed environment definitions. The library makes this simple:
export const awsConfig = new CdkConfig(awsSchema)
.set("dev", {
accountId: "123456789012",
region: "us-east-1",
})
.set("dev/staging", {
tags: { Environment: "staging" },
});
const stagingConfig = awsConfig.get("dev/staging");
// {
// accountId: '123456789012', // Inherited from 'dev'
// region: 'us-east-1', // Inherited from 'dev'
// tags: { Environment: 'staging' }
// }
Child environments inherit from sub paths, which means less copy-pasting and more consistency.
Adding to Your CDK Projects
We utilize the construct tree, and propagated context to pass the EnvId. Nested stacks and constructs can simply call .get(scope)
with either their parent's or their this
prop.
Integrating with CDK is straightforward:
import { App, Stack } from "aws-cdk-lib";
import { getEnvId, initialContext } from "@cdklib/config";
import { awsConfig } from "./config/aws";
import { eksConfig } from "./config/eks";
// Initialize app with an environment context
const app = new App({
context: initialContext("dev/staging"),
});
class MyInfraStack extends Stack {
constructor(scope: Construct, id: string) {
super(scope, id);
// Get configuration for this environment
const aws = awsConfig.get(this);
const eks = eksConfig.get(this);
// Use the configuration values in your constructs
new eks.Cluster(this, "EksCluster", {
clusterName: eks.clusterName,
version: eks.version,
nodeSize: eks.nodeSize,
minNodes: eks.minNodes,
maxNodes: eks.maxNodes,
tags: aws.tags,
});
}
}
The getEnvId
utility lets you access the environment ID from any construct, so you can get the right config wherever you need it.
Tags, specficially are better managed using Aspects, which I'll cover separately
CDKTF Integration
If you're using Terraform CDK, @cdklib/config
works just as well with it. You can use either an app-per-environment or stack-per-environment approach, depending on your workflow. The library integrates seamlessly with the CDKTF context system, similar to how it works with AWS CDK.
Check out the readme for more examples of CDKTF integration.
Wrapping Up
Managing configuration is one of those things that's easy to overlook but can make a huge difference in your daily CDK work. With @cdklib/config
, you get a simple, type-safe way to handle configuration across all your CDK projects.
The library is lightweight and can dramatically simplify how you manage environment settings in your infrastructure code.
The library is intentionally lightweight and simple. Copy it, modify it or contribute. I just want you to have a better time.
For more examples and best practices, take a look at the project readme.
This blog post is also available on my personal blog.
I'm wondering π€
- Are you using CDKs (cdk8s / cdktf / aws cdk)? Would you like to try it? What's stopping you?
- How are you managing config in your CDK apps?
- Any questions?
Top comments (0)