DEV Community

Cover image for CDK Deploy-Twice: When Your Infrastructure Needs to Know About Itself

CDK Deploy-Twice: When Your Infrastructure Needs to Know About Itself

There is a moment that catches a lot of people out who are new to AWS CDK. You deploy a service, the deploy succeeds, and then you realize the service cannot fully configure itself because it did not know its own endpoint until after it was running.

This is not a CDK bug. It is a genuine chicken-and-egg problem, and once you understand it, the solution is straightforward-ish.

The problem

Some resources only exist after CloudFormation has provisioned them: ALB endpoints, service URLs, and auto-assigned DNS names. These values are not known at the cdk synth time. They are outputs that come back after the stack deploys.

If your application needs to know its own public URL (e.g., to build redirect links), well then, you are in a kerfuffle. You cannot pass a value into the stack that the stack itself has not produced yet. CloudFormation is not psychic, so unfortunately neither is CDK (I hear AI is working on this, though).

What it looks like in practice

Here is a real example from my latest YouTube tutorial video. This is a URL shortener that needs BASE_URL to construct the short links it returns, but BASE_URL is the service's own endpoint, which CloudFormation only assigns after the ECS service and ALB are provisioned.

The CDK stack handles this with tryGetContext:

const baseUrl = this.node.tryGetContext('baseUrl') as string | undefined;

const environment = [
  { name: 'TABLE_NAME', value: table.tableName },
  { name: 'AWS_DEFAULT_REGION', value: this.region },
];

if (baseUrl) {
  environment.push({ name: 'BASE_URL', value: baseUrl });
}
Enter fullscreen mode Exit fullscreen mode

And the endpoint is exported as a stack output:

new cdk.CfnOutput(this, 'ServiceEndpoint', {
  value: service.attrEndpoint,
  description: 'Re-deploy with --context baseUrl=<this value> to wire BASE_URL',
});
Enter fullscreen mode Exit fullscreen mode

tryGetContext returns undefined if the value was not passed in, so deploy one works fine. It simply runs without BASE_URL set. Deploy two wires it in. Therefore, two deploys, one working service, zero existential crises.

The deploy pattern

Deploy 1: provision the infrastructure, get the endpoint:

cdk deploy EcsExpressStack
Enter fullscreen mode Exit fullscreen mode

Deploy 2: pass the endpoint back in as context:

SERVICE_URL=$(aws cloudformation describe-stacks \
  --stack-name EcsExpressStack \
  --query "Stacks[0].Outputs[?OutputKey=='ServiceEndpoint'].OutputValue" \
  --output text)

cdk deploy EcsExpressStack --context baseUrl=$SERVICE_URL
Enter fullscreen mode Exit fullscreen mode

Why this is not a CDK bug

CDK synthesizes a CloudFormation template before anything is deployed. At synth time, late-bound values like ALB endpoints exist only as CloudFormation tokens, which are placeholders that resolve later. You can use them within the same stack (they resolve correctly in the template), but you cannot read them back into your TypeScript logic during synth. This is because the template has not run yet, and therefore the value does not exist yet. This is simply the correct order of operations.

tryGetContext sidesteps this. You supply the value externally on a subsequent deploy, once CloudFormation has resolved it.

When you will run into this

  • A service that builds URLs pointing to itself
  • A resource that needs its own ARN or DNS name as a config value
  • Cross-stack references where stack B's input is stack A's output and you have not wired them through CfnOutput and Fn.importValue

The pattern feels a little awkward the first time. It stops feeling awkward once you understand why it works that way. Then starts feeling awkward again when you dust off that old forgotten side project (you know, that one).

So which came first: the service or the endpoint?

The endpoint... but only after the service... which needed the endpoint to configure itself... which required the service to exist first.

At this point, I recommend not thinking about it too hard.

Top comments (0)