AWS: Step-by-Step Guide to Cloudfront Invalidation of S3 assets in CodePipeline using Lambda

Stephen Lee (Sungsoo)
4 min readDec 4, 2022

--

Set up a Lambda function and CodePipeline to invalidate Cloudfront cache automatically.

Invalidate Cloudfront cache after deploying to S3.

Introduction

I built my website with React TypeScript and deployed it to S3 with Cloudfront via CodePipeine and CodeBuild.

This story will explain how I automated Cloudfront cache invalidation in the CodePipeline so I don’t have to run aws-cli manually to invalidate the cache. I’m sure there are some other ways to accomplish the automation and this is one way to do it (i.e. add the trigger to S3 events for Lambda to pick up the changes and invalidate the Cloudfront cache).

Let’s get started!

#1 Create resources.

First, ensure that you have resources created for CodePipeline. Refer to my Github for my CloudFormation code. If you already have CodePipeline and S3 setup, skip this.

How my resources are set up in AWS. Refer to my Github (CloudFormation).

#2 Create a Lambda function.

You can use arm64 CPU Architecture too.
  • Update the IAM role of the Lambda function. Here is what my IAM role and permission policy look like.
IAM Role
IAM Policy
I like using Visual Editor to add/remove permissions to the policy.
  • Add this code to the Lambda function’s Code Source and click deploy.
import json
import boto3

code_pipeline = boto3.client("codepipeline")
cloud_front = boto3.client("cloudfront")

def lambda_handler(event, context):
job_id = event["CodePipeline.job"]["id"]
try:
user_params = json.loads(
event["CodePipeline.job"]
["data"]
["actionConfiguration"]
["configuration"]
["UserParameters"]
)
cloud_front.create_invalidation(
DistributionId=user_params["distributionId"],
InvalidationBatch={
"Paths": {
"Quantity": len(user_params["objectPaths"]),
"Items": user_params["objectPaths"],
},
"CallerReference": event["CodePipeline.job"]["id"],
},
)
except Exception as e:
code_pipeline.put_job_failure_result(
jobId=job_id,
failureDetails={
"type": "JobFailed",
"message": str(e),
},
)
else:
code_pipeline.put_job_success_result(
jobId=job_id,
)
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": [
"codepipeline:PutJobFailureResult",
"codepipeline:PutJobSuccessResult",
"cloudfront:CreateInvalidation"
],
"Resource": "*"
},
{
"Sid": "VisualEditor1",
"Effect": "Allow",
"Action": "logs:CreateLogGroup",
"Resource": "arn:aws:logs:us-east-1:420504697712:*"
},
{
"Sid": "VisualEditor2",
"Effect": "Allow",
"Action": [
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": "arn:aws:logs:us-east-1:XXX:log-group:/aws/lambda/invalidate-cloudfront-cache-from-code-pipeline:*"
}
]
}
  • Once the Lambda function is created, it should look something like this.

#3 Add another step to your CodePipeline.

  • Update the IAM role for this CodePipeline to have these permissions: lambda:InvokeFunction and lambda:ListFunctions to allow CodePipeline to access Lambda resources.

The End

Run the CodePipeline. That’s it!

🎉🎉Happy building 🎉🎉

CodePipeline (left) Cloudfront (right)
Lambda Cloudwatch: There is one extra invocation by mistake (I added the wrong Cloudfront Distribution ID 😅).

I hope you found this guide helpful.

Live life to the fullest,

Stephen S. Lee

--

--