DEV Community

loading...
Cover image for AWS CDK: Per-Environment Configuration Patterns
AWS Community Builders

AWS CDK: Per-Environment Configuration Patterns

Gerald Stewart
AWS Community Builder (Dev-Tools)
・3 min read

Introduction 👋

Often projects will have different configuration values for each deployed environment. This could be feature toggles, URLs for third-party services or any number of other variables.

With the AWS CDK, this is simple to configure. I have seen a few different approaches to this problem. In this blog I'll share a few suitable for use in TypeScript.

Method 1️⃣: Stack Configuration Function

This approach uses a mapper function to return the configuration, you can see we have a single configuration property defined in the EnvironmentConfig interface.

interface IEnvironmentConfig {
  readonly myEnvSpecificApiEndpoint: string;
}

const environmentConfig = (environment: string): IEnvironmentConfig => {
  const environmentMapper: {
    [environment: string]: {
      myEnvSpecificApiEndpoint: string;
    };
  } = {
    local: {
      myEnvSpecificApiEndpoint: 'https://dev.google.com/api',
    },
    test: {
      myEnvSpecificApiEndpoint: 'https://test.google.com/api',
    },
    production: {
      myEnvSpecificApiEndpoint: 'https://google.com/api',
    },
  };
  return environmentMapper[environmentName];
};
Enter fullscreen mode Exit fullscreen mode

This function would be called from the stack like this, process.env.ENV_NAME would correspond to the environment name (replace this with your environment name variable for your chosen CI/CD pipeline) or default to local if undefined.

const environment: string = process.env.ENV_NAME || 'local';
const envConfig: IEnvironmentConfig = environmentConfig(environment);
Enter fullscreen mode Exit fullscreen mode

You can then access the configuration like this:

const apiEndpoint: string = envConfig.myEnvSpecificApiEndpoint;
Enter fullscreen mode Exit fullscreen mode

The apiEndpoint variable is now ready to be used in your stack.

Method 2️⃣: CDK Runtime Context

CDK Context values are key-value pairs that can be associated with a stack or construct. There are a number of different ways these values can be configured, for more information on that see the link above to the documentation.

In this example, I'm going to use cdk.context.json file in the root of a CDK project to configure a stack.

Here is an example cdk.context.json:

{
   "local":{
      "myEnvSpecificApiEndpoint":"https://dev.google.com/api"
   },
   "test":{
      "myEnvSpecificApiEndpoint":"https://test.google.com/api"
   },
   "production":{
      "myEnvSpecificApiEndpoint":"https://google.com/api"
   }
}
Enter fullscreen mode Exit fullscreen mode

Interfaces can also be created to define the type of configuration data expected:

interface IEnvironmentConfig {
  readonly myEnvSpecificApiEndpoint: string;
}
Enter fullscreen mode Exit fullscreen mode

These values can be accessed like this:

const environment: string = process.env.ENV_NAME || 'local';
const envConfig: IEnvironmentConfig = scope.node.tryGetContext(environment);
const apiEndpoint: string = envConfig.myEnvSpecificApiEndpoint;
Enter fullscreen mode Exit fullscreen mode

Method 3️⃣: Extending StackProps

The AWS CDK StackProps interface can be extended to add additional configuration properties. In this example we will extend the AWS CDK interface to add a property called myEnvSpecificApiEndpoint.

interface IEnvironmentConfig extends StackProps {
  readonly myEnvSpecificApiEndpoint: string;
}
Enter fullscreen mode Exit fullscreen mode

Now in the stack initialisation file (located in the bin directory) we can pass this in.

const app = new cdk.App();
new TheScheduledLambdaStack(app, 'TheScheduledLambdaStack',{
    myEnvSpecificApiEndpoint: 'https://dev.google.com/api'
});
Enter fullscreen mode Exit fullscreen mode

Now, the one downfall of this is that you still need to implement something like method 1 or 2 to configure it on a per-environment basis. This would look something like this for method 1:

new TheScheduledLambdaStack(app, 'TheScheduledLambdaStack',{
    myEnvSpecificApiEndpoint: envConfig.myEnvSpecificApiEndpoint;
});
Enter fullscreen mode Exit fullscreen mode

Conclusion 🤌

Looking at all three methods, I personally like method 2. Until recently I was blissfully unaware the CDK had already 'solved' this problem for us.

I live by the saying 'code is a liability' - the less code you manage the better.

Do you..

  • use any of these methods already?
  • have a better way of doing it?
  • have a different opinion on the optimal solution?

Let me know in the comments!

Discussion (1)

Collapse
eriklz profile image
Erik Lundevall Zara

I prefer to use method 3 for Constructs, and to some extent Stacks.

Per-environment data may come from configuration parameters you store in your project, from Systems Manager Parameter Store, Secrets Manager, other stacks, etc.
At least for Constructs I try to keep the interface the same, so it does not need to care which way the parameter data has been retrieved, thus always method 3 for those.

For Stacks, that may vary a bit.