Deployment circle with CloudFront and Terraform

The aws_cloudfront_distribution resource has a very concise config: its origins and cache behaviors are arguments instead of separate resources.

This helps with deployment time as that required only one 4-minute waiting.

But it also makes it easier to end up with a dependency circle. This is the most recent one I encountered:

There is a NodeJS app that verifies the JWT passed by the user:

const jwtVerifier = CognitoJwtVerifier.create({
	userPoolId: process.env.COGNITO_USER_POOL_ID,
	tokenUse: "access",
	clientId: process.env.COGNITO_CLIENT_ID,
});

This creates a circle:

The distribution uses a VPC origin that is an EC2 instance:

resource "aws_cloudfront_distribution" "distribution" {
  origin {
    origin_id                = "backend"
		vpc_origin_config {
			vpc_origin_id = aws_cloudfront_vpc_origin.backend.id
		}
  }
}

The vpc origin depends on the instance:

resource "aws_cloudfront_vpc_origin" "backend" {
  vpc_origin_endpoint_config {
    arn                    = aws_instance.backend.arn
  }
}

That instance has a setup script:

resource  "aws_instance" "backend" {
	user_data = local.user_data
}

That user data starts the NodeJS server that sets the Cognito User Pool client id:

locals {
	user_data = <<-EOF
#!/bin/bash
...
export COGNITO_USER_POOL_ID="${aws_cognito_user_pool.pool.id}"
export COGNITO_CLIENT_ID="${aws_cognito_user_pool_client.client.id}"
export PORT="8080"
EOF
}

But then the User Pool client needs the CloudFront distibution:

resource "aws_cognito_user_pool_client" "client" {
  callback_urls                        = ["https://${aws_cloudfront_distribution.distribution.domain_name}"]
}

If there was a way to separate the origins config from the CloudFront distribution resource this would be easy to configure: the Cognito client could be created after the distribution then the origins could be configured after.

January 8, 2025

Free PDF guide

Sign up to our newsletter and download the "Git Tips and Tricks" guide.