{"id":2771,"date":"2026-06-08T04:23:36","date_gmt":"2026-06-08T04:23:36","guid":{"rendered":"https:\/\/tucumandevelopers.com\/index.php\/2026\/06\/08\/cdk-deploy-twice-when-your-infrastructure-needs-to-know-about-itself\/"},"modified":"2026-06-08T04:23:36","modified_gmt":"2026-06-08T04:23:36","slug":"cdk-deploy-twice-when-your-infrastructure-needs-to-know-about-itself","status":"publish","type":"post","link":"https:\/\/tucumandevelopers.com\/index.php\/2026\/06\/08\/cdk-deploy-twice-when-your-infrastructure-needs-to-know-about-itself\/","title":{"rendered":"CDK Deploy-Twice: When Your Infrastructure Needs to Know About Itself"},"content":{"rendered":"<div>\n<div><\/div>\n<p>And the endpoint is exported as a stack output: <\/p>\n<div>\n<pre><code><span>new<\/span> <span>cdk<\/span><span>.<\/span><span>CfnOutput<\/span><span>(<\/span><span>this<\/span><span>,<\/span> <span>'<\/span><span>ServiceEndpoint<\/span><span>'<\/span><span>,<\/span> <span>{<\/span> <span>value<\/span><span>:<\/span> <span>service<\/span><span>.<\/span><span>attrEndpoint<\/span><span>,<\/span> <span>description<\/span><span>:<\/span> <span>'<\/span><span>Re-deploy with --context baseUrl=&lt;this value&gt; to wire BASE_URL<\/span><span>'<\/span><span>,<\/span> <span>});<\/span> <\/code><\/pre>\n<div>\n<\/p><\/div>\n<\/p><\/div>\n<p><code>tryGetContext<\/code> returns <code>undefined<\/code> if the value was not passed in, so deploy one works fine. It simply runs without <code>BASE_URL<\/code> set. Deploy two wires it in. Therefore, two deploys, one working service, zero existential crises.<\/p>\n<h2> <a name=\"the-deploy-pattern\" href=\"#the-deploy-pattern\"> <\/a> The deploy pattern <\/h2>\n<p><strong>Deploy 1:<\/strong> provision the infrastructure, get the endpoint: <\/p>\n<div>\n<pre><code>cdk deploy EcsExpressStack <\/code><\/pre>\n<div>\n<\/p><\/div>\n<\/p><\/div>\n<p><strong>Deploy 2:<\/strong> pass the endpoint back in as context: <\/p>\n<div>\n<pre><code><span>SERVICE_URL<\/span><span>=<\/span><span>$(<\/span>aws cloudformation describe-stacks <span>\\<\/span> <span>--stack-name<\/span> EcsExpressStack <span>\\<\/span> <span>--query<\/span> <span>\"Stacks[0].Outputs[?OutputKey=='ServiceEndpoint'].OutputValue\"<\/span> <span>\\<\/span> <span>--output<\/span> text<span>)<\/span> cdk deploy EcsExpressStack <span>--context<\/span> <span>baseUrl<\/span><span>=<\/span><span>$SERVICE_URL<\/span> <\/code><\/pre>\n<div>\n<\/p><\/div>\n<\/p><\/div>\n<h2> <a name=\"why-this-is-not-a-cdk-bug\" href=\"#why-this-is-not-a-cdk-bug\"> <\/a> Why this is not a CDK bug <\/h2>\n<p>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.<\/p>\n<p><code>tryGetContext<\/code> sidesteps this. You supply the value externally on a subsequent deploy, once CloudFormation has resolved it.<\/p>\n<h2> <a name=\"when-you-will-run-into-this\" href=\"#when-you-will-run-into-this\"> <\/a> When you will run into this <\/h2>\n<ul>\n<li>A service that builds URLs pointing to itself<\/li>\n<li>A resource that needs its own ARN or DNS name as a config value<\/li>\n<li>Cross-stack references where stack B&#8217;s input is stack A&#8217;s output and you have not wired them through <code>CfnOutput<\/code> and <code>Fn.importValue<\/code> <\/li>\n<\/ul>\n<p>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).<\/p>\n<h2> <a name=\"so-which-came-first-the-service-or-the-endpoint\" href=\"#so-which-came-first-the-service-or-the-endpoint\"> <\/a> So which came first: the service or the endpoint? <\/h2>\n<p>The endpoint&#8230; but only after the service&#8230; which needed the endpoint to configure itself&#8230; which required the service to exist first.<\/p>\n<p>At this point, I recommend not thinking about it too hard.<\/p>\n<\/p><\/div>\n<\/div>\n<\/div>\n<\/div>\n<p>Fuente: <a href=\"https:\/\/dev.to\/aws-builders\/cdk-deploy-twice-when-your-infrastructure-needs-to-know-about-itself-573g\">Art\u00edculo original<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>And the endpoint is exported as a stack output: new cdk.CfnOutput(this, &#8216;ServiceEndpoint&#8217;, { value: service.attrEndpoint, description: &#8216;Re-deploy with &#8211;context baseUrl=&lt;this value&gt; to wire BASE_URL&#8217;, }); 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 [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":2648,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":"","jetpack_publicize_message":"","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":true,"jetpack_social_options":{"image_generator_settings":{"template":"highway","default_image_id":0,"font":"","enabled":false},"version":2}},"categories":[41],"tags":[],"class_list":["post-2771","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-devto"],"jetpack_publicize_connections":[],"_links":{"self":[{"href":"https:\/\/tucumandevelopers.com\/index.php\/wp-json\/wp\/v2\/posts\/2771","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/tucumandevelopers.com\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/tucumandevelopers.com\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/tucumandevelopers.com\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/tucumandevelopers.com\/index.php\/wp-json\/wp\/v2\/comments?post=2771"}],"version-history":[{"count":0,"href":"https:\/\/tucumandevelopers.com\/index.php\/wp-json\/wp\/v2\/posts\/2771\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/tucumandevelopers.com\/index.php\/wp-json\/wp\/v2\/media\/2648"}],"wp:attachment":[{"href":"https:\/\/tucumandevelopers.com\/index.php\/wp-json\/wp\/v2\/media?parent=2771"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/tucumandevelopers.com\/index.php\/wp-json\/wp\/v2\/categories?post=2771"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/tucumandevelopers.com\/index.php\/wp-json\/wp\/v2\/tags?post=2771"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}