Nightmare infrastructure – episode 4. Just when you thought it couldn’t get any worse...
29 October 2025 - 1 min. read
Damiano Giorgi
DevOps Engineer
We have already written about Continuous Delivery/Continuous Integration several times, most of them mainly focused on the backend part of an application. Of course, web applications also require a front-end, thus, making the deployment process of the front-end agile and fast is crucial for the success of a project. In this article, we want to bring you the complete project for an AWS infrastructure to host an Angular application, complete with a CDN, a custom function to invalidate the cache and a fully automated CD pipeline.This project aims to fulfill the best practices and will provide a robust, highly scalable and fully managed solution for the hosting of front-end applications on AWS. This is also the most cost-optimized solution we have found so far, allowing you to serve your static content in a very reliable, performant and cheap way.Our architecture is logically partitioned in 2 parts, the customer-facing hosting, and the developer-facing Continuous Delivery infrastructure.
The diagram above shows the complete infrastructure, it also shows the triggers and the action taken during the pipeline execution.The customer-facing hosting is based on CloudFront, which allows us to serve the Angular application in a very efficient and reliable way. Using CloudFront is also required to enable HTTPS for our front-end application.The compiled assets of the front-end application will be stored inside an S3 Bucket, used as an origin for the CloudFront distribution. We strongly advise keeping the S3 Bucket private, in this way no external user will be able to directly access the files on S3, limiting costs and possibilities of exploits.We included Route53 in the architecture because it’s the most straightforward way to manage DNS records for the domain, anyway, any DNS service would do, as long as you can add a CNAME record for your front-end application.The real interesting part of the architecture is the developer-facing one. The core of the solution is CodePipeline, which we use as an orchestrator to react to changes made in the repository and pass the data between all the services used during the pipeline execution.We also used CodeCommit as a repository for the application code. At the time of writing, CodePipeline supports CodeCommit and GitHub. In order to support other Git repo configurations, further customization would be needed. In order to build the angular solution, we leverage CodeBuild to automatically provision a container for the build process.To speed up the deploy and avoid waiting for the expiration of each object in the CDN, we included an Invoke step at the end of the pipeline to run a Lambda Function which creates an invalidation request for the CloudFront Distribution.version: '0.2'
phases:
install:
runtime-versions:
nodejs: 12
pre_build:
commands:
- echo "Prebuild, installing npm dependencies"
- npm install
build:
commands:
- echo "Starting the build step"
- npm run build
- echo "Finished"
artifacts:
name: "BuildOutput"
files:
- '**/*'
base-directory: 'dist'
The output of the build process is an archive of the files and folders contained in the dist folder.import boto3
import os
from botocore.exceptions import ClientError
cloudfront_client = boto3.client('cloudfront')
codepipeline_client = boto3.client('codepipeline')
def lambda_handler(event, context):
try:
cdn_id = os.environ["CDN"]
cloudfront_client.create_invalidation(
DistributionId=cdn_id,
InvalidationBatch={
'Paths': {
'Quantity': 1,
'Items': [
'/*'
],
},
'CallerReference': event['CodePipeline.job']['id']
}
)
codepipeline_client.put_job_success_result(jobId=event['CodePipeline.job']['id'])
except ClientError as e:
print("Boto3 exception", e)
codepipeline_client.put_job_failure_result(
jobId=event['CodePipeline.job']['id'],
failureDetails={
'type': 'JobFailed',
'message': e.response
})
except Exception as e:
print("Error", e)
codepipeline_client.put_job_failure_result(
jobId=event['CodePipeline.job']['id'],
failureDetails={
'type': 'JobFailed',
'message': e.args
})
Still curious about Continuous Delivery/Continuous Integration?
Read also:
Leave a comment or contact us for questions or suggestions!
See you :)